From 673256199c33d5cbf14d56c19ea3829d36373020 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 4 May 2021 14:45:51 +0200 Subject: [PATCH] Refactor query method execution to use R2dbcEntityTemplate. Repository query methods are now executed through R2dbcEntityTemplate to participate in entity callbacks. Previously, query methods were executed directly using DatabaseClient which didn't allow for entity callbacks. Closes #591 --- .../r2dbc/core/R2dbcEntityOperations.java | 113 ++++++- .../data/r2dbc/core/R2dbcEntityTemplate.java | 150 +++++++-- .../data/r2dbc/mapping/SettableValue.java | 15 + .../repository/query/AbstractR2dbcQuery.java | 143 +++------ .../ExpressionEvaluatingParameterBinder.java | 42 +-- .../repository/query/PartTreeR2dbcQuery.java | 22 +- .../query/PreparedOperationBindableQuery.java | 2 +- .../repository/query/R2dbcQueryExecution.java | 11 +- .../query/StringBasedR2dbcQuery.java | 150 +++++++-- .../support/R2dbcRepositoryFactory.java | 14 +- ...stractR2dbcRepositoryIntegrationTests.java | 2 +- .../H2R2dbcRepositoryIntegrationTests.java | 44 +++ .../query/PartTreeR2dbcQueryUnitTests.java | 287 +++++++++--------- .../query/StringBasedR2dbcQueryUnitTests.java | 123 ++++---- 14 files changed, 705 insertions(+), 413 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityOperations.java b/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityOperations.java index 37f9461b..a8a1c30b 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityOperations.java +++ b/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityOperations.java @@ -15,15 +15,23 @@ */ package org.springframework.data.r2dbc.core; +import io.r2dbc.spi.Row; +import io.r2dbc.spi.RowMetadata; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.function.BiFunction; +import java.util.function.Function; + import org.springframework.dao.DataAccessException; import org.springframework.dao.TransientDataAccessResourceException; import org.springframework.data.r2dbc.convert.R2dbcConverter; import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.query.Update; import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.PreparedOperation; +import org.springframework.r2dbc.core.RowsFetchSpec; +import org.springframework.util.Assert; /** * Interface specifying a basic set of reactive R2DBC operations using entities. Implemented by @@ -95,7 +103,7 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations { * Execute a {@code SELECT} query and convert the resulting items to a stream of entities. * * @param query must not be {@literal null}. - * @param entityClass The entity type must not be {@literal null}. + * @param entityClass the entity type must not be {@literal null}. * @return the result objects returned by the action. * @throws DataAccessException if there is any problem issuing the execution. */ @@ -105,7 +113,7 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations { * Execute a {@code SELECT} query and convert the resulting item to an entity ensuring exactly one result. * * @param query must not be {@literal null}. - * @param entityClass The entity type must not be {@literal null}. + * @param entityClass the entity type must not be {@literal null}. * @return exactly one result or {@link Mono#empty()} if no match found. * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. * @throws DataAccessException if there is any problem issuing the execution. @@ -117,7 +125,7 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations { * * @param query must not be {@literal null}. * @param update must not be {@literal null}. - * @param entityClass The entity type must not be {@literal null}. + * @param entityClass the entity type must not be {@literal null}. * @return the number of affected rows. * @throws DataAccessException if there is any problem executing the query. */ @@ -127,12 +135,105 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations { * 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}. + * @param entityClass the entity type must not be {@literal null}. * @return the number of affected rows. * @throws DataAccessException if there is any problem issuing the execution. */ Mono delete(Query query, Class entityClass) throws DataAccessException; + // ------------------------------------------------------------------------- + // Methods dealing with org.springframework.r2dbc.core.PreparedOperation + // ------------------------------------------------------------------------- + + /** + * Execute a query for a {@link RowsFetchSpec}, given {@link PreparedOperation}. Any provided bindings within + * {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The query is issued as-is without + * additional pre-processing such as named parameter expansion. Results of the query are mapped onto + * {@code entityClass}. + * + * @param operation the prepared operation wrapping a SQL query and bind parameters. + * @param entityClass the entity type must not be {@literal null}. + * @return a {@link RowsFetchSpec} ready to materialize. + * @since 1.4 + * @throws DataAccessException if there is any problem issuing the execution. + */ + RowsFetchSpec query(PreparedOperation operation, Class entityClass) throws DataAccessException; + + /** + * Execute a query for a {@link RowsFetchSpec}, given {@link PreparedOperation}. Any provided bindings within + * {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The query is issued as-is without + * additional pre-processing such as named parameter expansion. Results of the query are mapped using {@link Function + * rowMapper}. + * + * @param operation the prepared operation wrapping a SQL query and bind parameters. + * @param rowMapper the row mapper must not be {@literal null}. + * @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize. + * @throws DataAccessException if there is any problem issuing the execution. + * @since 1.4 + * @see #query(PreparedOperation, BiFunction) + */ + default RowsFetchSpec query(PreparedOperation operation, Function rowMapper) + throws DataAccessException { + + Assert.notNull(rowMapper, "Row mapper must not be null"); + + return query(operation, ((row, rowMetadata) -> rowMapper.apply(row))); + } + + /** + * Execute a query for a {@link RowsFetchSpec}, given {@link PreparedOperation}. Any provided bindings within + * {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The query is issued as-is without + * additional pre-processing such as named parameter expansion. Results of the query are mapped using + * {@link BiFunction rowMapper}. + * + * @param operation the prepared operation wrapping a SQL query and bind parameters. + * @param rowMapper the row mapper must not be {@literal null}. + * @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize. + * @since 1.4 + * @throws DataAccessException if there is any problem issuing the execution. + */ + RowsFetchSpec query(PreparedOperation operation, BiFunction rowMapper) + throws DataAccessException; + + /** + * Execute a query for a {@link RowsFetchSpec} in the context of {@code entityClass}, given {@link PreparedOperation}. + * Any provided bindings within {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The + * query is issued as-is without additional pre-processing such as named parameter expansion. Results of the query are + * mapped using {@link Function rowMapper}. + * + * @param operation the prepared operation wrapping a SQL query and bind parameters. + * @param entityClass the entity type must not be {@literal null}. + * @param rowMapper the row mapper must not be {@literal null}. + * @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize. + * @throws DataAccessException if there is any problem issuing the execution. + * @since 1.4 + * @see #query(PreparedOperation, Class, BiFunction) + */ + default RowsFetchSpec query(PreparedOperation operation, Class entityClass, Function rowMapper) + throws DataAccessException { + + Assert.notNull(rowMapper, "Row mapper must not be null"); + + return query(operation, entityClass, ((row, rowMetadata) -> rowMapper.apply(row))); + } + + /** + * Execute a query for a {@link RowsFetchSpec} in the context of {@code entityClass}, given {@link PreparedOperation}. + * Any provided bindings within {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The + * query is issued as-is without additional pre-processing such as named parameter expansion. Results of the query are + * mapped using {@link BiFunction rowMapper}. + * + * @param operation the prepared operation wrapping a SQL query and bind parameters. + * @param entityClass the entity type must not be {@literal null}. + * @param rowMapper the row mapper must not be {@literal null}. + * @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize. + * @throws DataAccessException if there is any problem issuing the execution. + * @since 1.4 + * @see #query(PreparedOperation, Class, BiFunction) + */ + RowsFetchSpec query(PreparedOperation operation, Class entityClass, + BiFunction rowMapper) throws DataAccessException; + // ------------------------------------------------------------------------- // Methods dealing with entities // ------------------------------------------------------------------------- @@ -140,7 +241,7 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations { /** * Insert the given entity and emit the entity if the insert was applied. * - * @param entity The entity to insert, must not be {@literal null}. + * @param entity the entity to insert, must not be {@literal null}. * @return the inserted entity. * @throws DataAccessException if there is any problem issuing the execution. */ @@ -149,7 +250,7 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations { /** * Update the given entity and emit the entity if the update was applied. * - * @param entity The entity to update, must not be {@literal null}. + * @param entity the entity to update, must not be {@literal null}. * @return the updated entity. * @throws DataAccessException if there is any problem issuing the execution. * @throws TransientDataAccessResourceException if the update did not affect any rows. diff --git a/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java b/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java index 2ee6a555..24c172cd 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java +++ b/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java @@ -307,7 +307,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw public Mono count(Query query, Class entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); - Assert.notNull(entityClass, "entity class must not be null"); + Assert.notNull(entityClass, "Entity class must not be null"); return doCount(query, entityClass, getTableName(entityClass)); } @@ -344,7 +344,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw public Mono exists(Query query, Class entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); - Assert.notNull(entityClass, "entity class must not be null"); + Assert.notNull(entityClass, "Entity class must not be null"); return doExists(query, entityClass, getTableName(entityClass)); } @@ -383,7 +383,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw public Flux select(Query query, Class entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); - Assert.notNull(entityClass, "entity class must not be null"); + Assert.notNull(entityClass, "Entity class must not be null"); SqlIdentifier tableName = getTableName(entityClass); return doSelect(query, entityClass, tableName, entityClass, RowsFetchSpec::all); @@ -432,24 +432,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw PreparedOperation operation = statementMapper.getMappedObject(selectSpec); - boolean simpleType; - BiFunction rowMapper; - if (returnType.isInterface()) { - simpleType = getConverter().isSimpleType(entityClass); - rowMapper = dataAccessStrategy.getRowMapper(entityClass) - .andThen(o -> projectionFactory.createProjection(returnType, o)); - } else { - simpleType = getConverter().isSimpleType(returnType); - rowMapper = dataAccessStrategy.getRowMapper(returnType); - } - - // avoid top-level null values if the read type is a simple one (e.g. SELECT MAX(age) via Integer.class) - if (simpleType) { - return new UnwrapOptionalFetchSpecAdapter<>(this.databaseClient.sql(operation) - .map((row, metadata) -> Optional.ofNullable(rowMapper.apply(row, metadata)))); - } - - return this.databaseClient.sql(operation).map(rowMapper); + return getRowsFetchSpec(databaseClient.sql(operation), entityClass, returnType); } /* @@ -470,7 +453,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw Assert.notNull(query, "Query must not be null"); Assert.notNull(update, "Update must not be null"); - Assert.notNull(entityClass, "entity class must not be null"); + Assert.notNull(entityClass, "Entity class must not be null"); return doUpdate(query, update, entityClass, getTableName(entityClass)); } @@ -499,7 +482,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw public Mono delete(Query query, Class entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); - Assert.notNull(entityClass, "entity class must not be null"); + Assert.notNull(entityClass, "Entity class must not be null"); return doDelete(query, entityClass, getTableName(entityClass)); } @@ -508,18 +491,64 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass); - StatementMapper.DeleteSpec selectSpec = statementMapper // + StatementMapper.DeleteSpec deleteSpec = statementMapper // .createDelete(tableName); Optional criteria = query.getCriteria(); if (criteria.isPresent()) { - selectSpec = criteria.map(selectSpec::withCriteria).orElse(selectSpec); + deleteSpec = criteria.map(deleteSpec::withCriteria).orElse(deleteSpec); } - PreparedOperation operation = statementMapper.getMappedObject(selectSpec); + PreparedOperation operation = statementMapper.getMappedObject(deleteSpec); return this.databaseClient.sql(operation).fetch().rowsUpdated().defaultIfEmpty(0); } + // ------------------------------------------------------------------------- + // Methods dealing with org.springframework.r2dbc.core.PreparedOperation + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#query(org.springframework.r2dbc.core.PreparedOperation, java.lang.Class) + */ + @Override + public RowsFetchSpec query(PreparedOperation operation, Class entityClass) { + + Assert.notNull(operation, "PreparedOperation must not be null"); + Assert.notNull(entityClass, "Entity class must not be null"); + + return new EntityCallbackAdapter<>(getRowsFetchSpec(databaseClient.sql(operation), entityClass, entityClass), + getTableNameOrEmpty(entityClass)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#query(org.springframework.r2dbc.core.PreparedOperation, java.util.function.BiFunction) + */ + @Override + public RowsFetchSpec query(PreparedOperation operation, BiFunction rowMapper) { + + Assert.notNull(operation, "PreparedOperation must not be null"); + Assert.notNull(rowMapper, "Row mapper must not be null"); + + return new EntityCallbackAdapter<>(databaseClient.sql(operation).map(rowMapper), SqlIdentifier.EMPTY); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#query(org.springframework.r2dbc.core.PreparedOperation, java.lang.Class, java.util.function.BiFunction) + */ + @Override + public RowsFetchSpec query(PreparedOperation operation, Class entityClass, + BiFunction rowMapper) { + + Assert.notNull(operation, "PreparedOperation must not be null"); + Assert.notNull(entityClass, "Entity class must not be null"); + Assert.notNull(rowMapper, "Row mapper must not be null"); + + return new EntityCallbackAdapter<>(databaseClient.sql(operation).map(rowMapper), getTableNameOrEmpty(entityClass)); + } + // ------------------------------------------------------------------------- // Methods dealing with entities // ------------------------------------------------------------------------- @@ -817,6 +846,13 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw return getRequiredEntity(entityClass).getTableName(); } + SqlIdentifier getTableNameOrEmpty(Class entityClass) { + + RelationalPersistentEntity entity = this.mappingContext.getPersistentEntity(entityClass); + + return entity != null ? entity.getTableName() : SqlIdentifier.EMPTY; + } + private RelationalPersistentEntity getRequiredEntity(Class entityClass) { return this.mappingContext.getRequiredPersistentEntity(entityClass); } @@ -846,6 +882,30 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw return query.getColumns().stream().map(table::column).collect(Collectors.toList()); } + private RowsFetchSpec getRowsFetchSpec(DatabaseClient.GenericExecuteSpec executeSpec, Class entityClass, + Class returnType) { + + boolean simpleType; + + BiFunction rowMapper; + if (returnType.isInterface()) { + simpleType = getConverter().isSimpleType(entityClass); + rowMapper = dataAccessStrategy.getRowMapper(entityClass) + .andThen(o -> projectionFactory.createProjection(returnType, o)); + } else { + simpleType = getConverter().isSimpleType(returnType); + rowMapper = dataAccessStrategy.getRowMapper(returnType); + } + + // avoid top-level null values if the read type is a simple one (e.g. SELECT MAX(age) via Integer.class) + if (simpleType) { + return new UnwrapOptionalFetchSpecAdapter<>( + executeSpec.map((row, metadata) -> Optional.ofNullable(rowMapper.apply(row, metadata)))); + } + + return executeSpec.map(rowMapper); + } + private static ReactiveDataAccessStrategy getDataAccessStrategy( org.springframework.data.r2dbc.core.DatabaseClient databaseClient) { @@ -989,6 +1049,11 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw } } + /** + * {@link RowsFetchSpec} adapter emitting values from {@link Optional} if they exist. + * + * @param + */ private static class UnwrapOptionalFetchSpecAdapter implements RowsFetchSpec { private final RowsFetchSpec> delegate; @@ -1012,4 +1077,37 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw return delegate.all().handle((optional, sink) -> optional.ifPresent(sink::next)); } } + + /** + * {@link RowsFetchSpec} adapter applying {@link #maybeCallAfterConvert(Object, SqlIdentifier)} to each emitted + * object. + * + * @param + */ + private class EntityCallbackAdapter implements RowsFetchSpec { + + private final RowsFetchSpec delegate; + private final SqlIdentifier tableName; + + private EntityCallbackAdapter(RowsFetchSpec delegate, SqlIdentifier tableName) { + this.delegate = delegate; + this.tableName = tableName; + } + + @Override + public Mono one() { + return delegate.one().flatMap(it -> maybeCallAfterConvert(it, tableName)); + } + + @Override + public Mono first() { + return delegate.first().flatMap(it -> maybeCallAfterConvert(it, tableName)); + } + + @Override + public Flux all() { + return delegate.all().flatMap(it -> maybeCallAfterConvert(it, tableName)); + } + } + } diff --git a/src/main/java/org/springframework/data/r2dbc/mapping/SettableValue.java b/src/main/java/org/springframework/data/r2dbc/mapping/SettableValue.java index b3999fa1..45dba48a 100644 --- a/src/main/java/org/springframework/data/r2dbc/mapping/SettableValue.java +++ b/src/main/java/org/springframework/data/r2dbc/mapping/SettableValue.java @@ -71,6 +71,21 @@ public class SettableValue { return new SettableValue(Parameter.empty(type)); } + /** + * Factory method to create a {@link SettableValue} from {@link Parameter}. Retains empty/type information. + * + * @param parameter the parameter to create a {@link SettableValue} from. + * @return a new {@link SettableValue} from {@link Parameter}. + * @since 1.4 + */ + public static SettableValue fromParameter(Parameter parameter) { + + Assert.notNull(parameter, "Parameter must not be null"); + + return parameter.isEmpty() ? SettableValue.empty(parameter.getType()) + : SettableValue.fromOrEmpty(parameter.getValue(), parameter.getType()); + } + /** * Returns the column value. Can be {@literal null}. * diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java index 96955c0e..fb487acb 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java @@ -15,19 +15,15 @@ */ package org.springframework.data.r2dbc.repository.query; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.Optional; - import org.reactivestreams.Publisher; import org.springframework.data.mapping.model.EntityInstantiators; -import org.springframework.data.r2dbc.convert.EntityRowMapper; import org.springframework.data.r2dbc.convert.R2dbcConverter; +import org.springframework.data.r2dbc.core.R2dbcEntityOperations; import org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution.ResultProcessingConverter; import org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution.ResultProcessingExecution; -import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor; import org.springframework.data.repository.query.ParameterAccessor; @@ -35,8 +31,8 @@ import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.util.ReflectionUtils; -import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.r2dbc.core.FetchSpec; +import org.springframework.r2dbc.core.PreparedOperation; import org.springframework.r2dbc.core.RowsFetchSpec; import org.springframework.util.Assert; @@ -49,25 +45,26 @@ import org.springframework.util.Assert; public abstract class AbstractR2dbcQuery implements RepositoryQuery { private final R2dbcQueryMethod method; - private final DatabaseClient databaseClient; + private final R2dbcEntityOperations entityOperations; private final R2dbcConverter converter; private final EntityInstantiators instantiators; /** - * Creates a new {@link AbstractR2dbcQuery} from the given {@link R2dbcQueryMethod} and {@link DatabaseClient}. + * Creates a new {@link AbstractR2dbcQuery} from the given {@link R2dbcQueryMethod} and {@link R2dbcEntityOperations}. * * @param method must not be {@literal null}. - * @param databaseClient must not be {@literal null}. + * @param entityOperations must not be {@literal null}. * @param converter must not be {@literal null}. + * @since 1.4 */ - public AbstractR2dbcQuery(R2dbcQueryMethod method, DatabaseClient databaseClient, R2dbcConverter converter) { + public AbstractR2dbcQuery(R2dbcQueryMethod method, R2dbcEntityOperations entityOperations, R2dbcConverter converter) { Assert.notNull(method, "R2dbcQueryMethod must not be null!"); - Assert.notNull(databaseClient, "DatabaseClient must not be null!"); + Assert.notNull(entityOperations, "R2dbcEntityOperations must not be null!"); Assert.notNull(converter, "R2dbcConverter must not be null!"); this.method = method; - this.databaseClient = databaseClient; + this.entityOperations = entityOperations; this.converter = converter; this.instantiators = new EntityInstantiators(); } @@ -91,38 +88,25 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery { return createQuery(parameterAccessor).flatMapMany(it -> executeQuery(parameterAccessor, it)); } - @SuppressWarnings({ "unchecked", "rawtypes" }) - private Publisher executeQuery(RelationalParameterAccessor parameterAccessor, BindableQuery it) { + @SuppressWarnings("unchecked") + private Publisher executeQuery(RelationalParameterAccessor parameterAccessor, PreparedOperation operation) { ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor); - DatabaseClient.GenericExecuteSpec boundQuery = it.bind(databaseClient.sql(it)); - FetchSpec fetchSpec; + RowsFetchSpec fetchSpec; - if (isExistsQuery()) { - fetchSpec = (FetchSpec) boundQuery.map(row -> true); - } else if (requiresMapping()) { - - Class typeToRead = resolveResultType(processor); - EntityRowMapper rowMapper = new EntityRowMapper<>(typeToRead, converter); - - if (converter.isSimpleType(typeToRead)) { - fetchSpec = new UnwrapOptionalFetchSpecAdapter<>( - boundQuery.map((row, rowMetadata) -> Optional.ofNullable(rowMapper.apply(row, rowMetadata)))); - - } else { - fetchSpec = new FetchSpecAdapter<>(boundQuery.map(rowMapper)); - } + if (isModifyingQuery()) { + fetchSpec = entityOperations.getDatabaseClient().sql(operation).fetch(); + } else if (isExistsQuery()) { + fetchSpec = entityOperations.getDatabaseClient().sql(operation).map(row -> true); } else { - fetchSpec = (FetchSpec) boundQuery.fetch(); + fetchSpec = entityOperations.query(operation, resolveResultType(processor)); } - SqlIdentifier tableName = method.getEntityInformation().getTableName(); - R2dbcQueryExecution execution = new ResultProcessingExecution(getExecutionToWrap(processor.getReturnedType()), new ResultProcessingConverter(processor, converter.getMappingContext(), instantiators)); - return execution.execute(fetchSpec, processor.getReturnedType().getDomainType(), tableName); + return execution.execute(RowsFetchSpec.class.cast(fetchSpec)); } Class resolveResultType(ResultProcessor resultProcessor) { @@ -136,45 +120,47 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery { return returnedType.isProjecting() ? returnedType.getDomainType() : returnedType.getReturnedType(); } - private boolean requiresMapping() { - return !isModifyingQuery(); - } - private R2dbcQueryExecution getExecutionToWrap(ReturnedType returnedType) { if (isModifyingQuery()) { - if (Boolean.class.isAssignableFrom(returnedType.getReturnedType())) { - return (q, t, c) -> q.rowsUpdated().map(integer -> integer > 0); + return fetchSpec -> { + + Assert.isInstanceOf(FetchSpec.class, fetchSpec); + + FetchSpec fs = (FetchSpec) fetchSpec; + + if (Boolean.class.isAssignableFrom(returnedType.getReturnedType())) { + return fs.rowsUpdated().map(integer -> integer > 0); } if (Number.class.isAssignableFrom(returnedType.getReturnedType())) { - return (q, t, c) -> q.rowsUpdated().map(integer -> { - return converter.getConversionService().convert(integer, returnedType.getReturnedType()); - }); + return fs.rowsUpdated() + .map(integer -> converter.getConversionService().convert(integer, returnedType.getReturnedType())); } if (ReflectionUtils.isVoid(returnedType.getReturnedType())) { - return (q, t, c) -> q.rowsUpdated().then(); + return fs.rowsUpdated().then(); } - return (q, t, c) -> q.rowsUpdated(); + return fs.rowsUpdated(); + }; } if (isCountQuery()) { - return (q, t, c) -> q.first().defaultIfEmpty(0L); + return (fetchSpec) -> fetchSpec.first().defaultIfEmpty(0L); } if (isExistsQuery()) { - return (q, t, c) -> q.first().defaultIfEmpty(false); + return (fetchSpec) -> fetchSpec.first().defaultIfEmpty(false); } if (method.isCollectionQuery()) { - return (q, t, c) -> q.all(); + return RowsFetchSpec::all; } - return (q, t, c) -> q.one(); + return RowsFetchSpec::one; } /** @@ -207,63 +193,6 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery { * @param accessor must not be {@literal null}. * @return a mono emitting a {@link BindableQuery}. */ - protected abstract Mono createQuery(RelationalParameterAccessor accessor); + protected abstract Mono> createQuery(RelationalParameterAccessor accessor); - private static class FetchSpecAdapter implements FetchSpec { - - private final RowsFetchSpec delegate; - - private FetchSpecAdapter(RowsFetchSpec delegate) { - this.delegate = delegate; - } - - @Override - public Mono one() { - return delegate.one(); - } - - @Override - public Mono first() { - return delegate.first(); - } - - @Override - public Flux all() { - return delegate.all(); - } - - @Override - public Mono rowsUpdated() { - throw new UnsupportedOperationException("Not supported after applying a row mapper"); - } - } - - private static class UnwrapOptionalFetchSpecAdapter implements FetchSpec { - - private final RowsFetchSpec> delegate; - - private UnwrapOptionalFetchSpecAdapter(RowsFetchSpec> delegate) { - this.delegate = delegate; - } - - @Override - public Mono one() { - return delegate.one().handle((optional, sink) -> optional.ifPresent(sink::next)); - } - - @Override - public Mono first() { - return delegate.first().handle((optional, sink) -> optional.ifPresent(sink::next)); - } - - @Override - public Flux all() { - return delegate.all().handle((optional, sink) -> optional.ifPresent(sink::next)); - } - - @Override - public Mono rowsUpdated() { - throw new UnsupportedOperationException("Not supported after applying a row mapper"); - } - } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java b/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java index de334380..1df069de 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java @@ -26,7 +26,7 @@ import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; -import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.binding.BindTarget; /** * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a @@ -58,42 +58,35 @@ class ExpressionEvaluatingParameterBinder { * Bind values provided by {@link RelationalParameterAccessor} to placeholders in {@link ExpressionQuery} while * considering potential conversions and parameter types. * - * @param bindSpec must not be {@literal null}. + * @param bindTarget must not be {@literal null}. * @param parameterAccessor must not be {@literal null}. * @param evaluator must not be {@literal null}. */ - DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec, + void bind(BindTarget bindTarget, RelationalParameterAccessor parameterAccessor, R2dbcSpELExpressionEvaluator evaluator) { Object[] values = parameterAccessor.getValues(); Parameters bindableParameters = parameterAccessor.getBindableParameters(); - DatabaseClient.GenericExecuteSpec bindSpecToUse = bindExpressions(bindSpec, evaluator); - bindSpecToUse = bindParameters(bindSpecToUse, parameterAccessor.hasBindableNullValue(), values, bindableParameters); - - return bindSpecToUse; + bindExpressions(bindTarget, evaluator); + bindParameters(bindTarget, parameterAccessor.hasBindableNullValue(), values, bindableParameters); } - private DatabaseClient.GenericExecuteSpec bindExpressions(DatabaseClient.GenericExecuteSpec bindSpec, + private void bindExpressions(BindTarget bindSpec, R2dbcSpELExpressionEvaluator evaluator) { - DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec; - for (ParameterBinding binding : expressionQuery.getBindings()) { org.springframework.r2dbc.core.Parameter valueForBinding = getBindValue( evaluator.evaluate(binding.getExpression())); - bindSpecToUse = bind(bindSpecToUse, binding.getParameterName(), valueForBinding); + bind(bindSpec, binding.getParameterName(), valueForBinding); } - - return bindSpecToUse; } - private DatabaseClient.GenericExecuteSpec bindParameters(DatabaseClient.GenericExecuteSpec bindSpec, + private void bindParameters(BindTarget bindSpec, boolean hasBindableNullValue, Object[] values, Parameters bindableParameters) { - DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec; int bindingIndex = 0; for (Parameter bindableParameter : bindableParameters) { @@ -109,7 +102,7 @@ class ExpressionEvaluatingParameterBinder { org.springframework.r2dbc.core.Parameter parameter = getBindValue(values, bindableParameter); if (!parameter.isEmpty() || hasBindableNullValue) { - bindSpecToUse = bind(bindSpecToUse, name.get(), parameter); + bind(bindSpec, name.get(), parameter); } // skip unused named parameters if there is SpEL @@ -118,12 +111,10 @@ class ExpressionEvaluatingParameterBinder { org.springframework.r2dbc.core.Parameter parameter = getBindValue(values, bindableParameter); if (!parameter.isEmpty() || hasBindableNullValue) { - bindSpecToUse = bind(bindSpecToUse, bindingIndex++, parameter); + bind(bindSpec, bindingIndex++, parameter); } } } - - return bindSpecToUse; } private org.springframework.r2dbc.core.Parameter getBindValue(Object[] values, Parameter bindableParameter) { @@ -134,26 +125,25 @@ class ExpressionEvaluatingParameterBinder { return dataAccessStrategy.getBindValue(parameter); } - private static DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec spec, String name, + private static void bind(BindTarget spec, String name, org.springframework.r2dbc.core.Parameter parameter) { Object value = parameter.getValue(); if (value == null) { - return spec.bindNull(name, parameter.getType()); + spec.bindNull(name, parameter.getType()); } else { - return spec.bind(name, value); + spec.bind(name, value); } } - private static DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec spec, int index, + private static void bind(BindTarget spec, int index, org.springframework.r2dbc.core.Parameter parameter) { Object value = parameter.getValue(); if (value == null) { - return spec.bindNull(index, parameter.getType()); + spec.bindNull(index, parameter.getType()); } else { - - return spec.bind(index, value); + spec.bind(index, value); } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java index 2fbfddfd..b7f4487d 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java @@ -23,6 +23,7 @@ import java.util.List; import org.springframework.data.domain.Sort; import org.springframework.data.r2dbc.convert.R2dbcConverter; +import org.springframework.data.r2dbc.core.R2dbcEntityOperations; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.relational.repository.query.RelationalEntityMetadata; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; @@ -52,13 +53,13 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery { * {@link R2dbcConverter} and {@link ReactiveDataAccessStrategy}. * * @param method query method, must not be {@literal null}. - * @param databaseClient database client, must not be {@literal null}. + * @param entityOperations entity operations, must not be {@literal null}. * @param converter converter, must not be {@literal null}. * @param dataAccessStrategy data access strategy, must not be {@literal null}. */ - public PartTreeR2dbcQuery(R2dbcQueryMethod method, DatabaseClient databaseClient, R2dbcConverter converter, + public PartTreeR2dbcQuery(R2dbcQueryMethod method, R2dbcEntityOperations entityOperations, R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy) { - super(method, databaseClient, converter); + super(method, entityOperations, converter); this.processor = method.getResultProcessor(); this.dataAccessStrategy = dataAccessStrategy; @@ -105,7 +106,7 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery { * @see org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery#createQuery(org.springframework.data.relational.repository.query.RelationalParameterAccessor) */ @Override - protected Mono createQuery(RelationalParameterAccessor accessor) { + protected Mono> createQuery(RelationalParameterAccessor accessor) { return Mono.fromSupplier(() -> { @@ -119,13 +120,20 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery { RelationalEntityMetadata entityMetadata = getQueryMethod().getEntityInformation(); R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor, projectedProperties); - PreparedOperation preparedQuery = queryCreator.createQuery(getDynamicSort(accessor)); - - return new PreparedOperationBindableQuery(preparedQuery); + return queryCreator.createQuery(getDynamicSort(accessor)); }); } private Sort getDynamicSort(RelationalParameterAccessor accessor) { return parameters.potentiallySortsDynamically() ? accessor.getSort() : Sort.unsorted(); } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getSimpleName()); + sb.append(" [").append(getQueryMethod().getName()); + sb.append(']'); + return sb.toString(); + } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java index 74b6c4dc..4114e79f 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java @@ -59,7 +59,7 @@ class PreparedOperationBindableQuery implements BindableQuery { * This class adapts {@link DatabaseClient.GenericExecuteSpec} to {@link BindTarget} allowing easy binding of query * parameters using {@link PreparedOperation}. */ - private static class BindSpecBindTargetAdapter implements BindTarget { + static class BindSpecBindTargetAdapter implements BindTarget { DatabaseClient.GenericExecuteSpec bindSpec; diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java index d59cde1a..c129cac2 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java @@ -25,12 +25,11 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.EntityInstantiators; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.repository.query.DtoInstantiatingConverter; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.util.ReflectionUtils; -import org.springframework.r2dbc.core.FetchSpec; +import org.springframework.r2dbc.core.RowsFetchSpec; import org.springframework.util.ClassUtils; /** @@ -41,7 +40,7 @@ import org.springframework.util.ClassUtils; */ interface R2dbcQueryExecution { - Publisher execute(FetchSpec query, Class type, SqlIdentifier tableName); + Publisher execute(RowsFetchSpec fetchSpec); /** * An {@link R2dbcQueryExecution} that wraps the results of the given delegate with the given result processing. @@ -57,11 +56,11 @@ interface R2dbcQueryExecution { } /* (non-Javadoc) - * @see org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution#execute(org.springframework.data.r2dbc.function.FetchSpec, java.lang.Class, java.lang.String) + * @see org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution#execute(org.springframework.data.r2dbc.function.FetchSpec) */ @Override - public Publisher execute(FetchSpec query, Class type, SqlIdentifier tableName) { - return (Publisher) this.converter.convert(this.delegate.execute(query, type, tableName)); + public Publisher execute(RowsFetchSpec fetchSpec) { + return (Publisher) this.converter.convert(this.delegate.execute(fetchSpec)); } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java index b33a6d1e..bc5fd30d 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java @@ -18,10 +18,16 @@ package org.springframework.data.r2dbc.repository.query; import reactor.core.publisher.Mono; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; import org.springframework.data.r2dbc.convert.R2dbcConverter; +import org.springframework.data.r2dbc.core.R2dbcEntityOperations; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; +import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.r2dbc.repository.Query; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; @@ -31,6 +37,9 @@ import org.springframework.data.spel.ExpressionDependencies; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.Parameter; +import org.springframework.r2dbc.core.PreparedOperation; +import org.springframework.r2dbc.core.binding.BindTarget; import org.springframework.util.Assert; /** @@ -48,24 +57,24 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { private final ExpressionParser expressionParser; private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider; private final ExpressionDependencies expressionDependencies; + private final ReactiveDataAccessStrategy dataAccessStrategy; /** * Creates a new {@link StringBasedR2dbcQuery} for the given {@link StringBasedR2dbcQuery}, {@link DatabaseClient}, * {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}. * * @param queryMethod must not be {@literal null}. - * @param databaseClient must not be {@literal null}. + * @param entityOperations must not be {@literal null}. * @param converter must not be {@literal null}. * @param dataAccessStrategy must not be {@literal null}. * @param expressionParser must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. */ - public StringBasedR2dbcQuery(R2dbcQueryMethod queryMethod, DatabaseClient databaseClient, R2dbcConverter converter, - ReactiveDataAccessStrategy dataAccessStrategy, - ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { - this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, databaseClient, converter, dataAccessStrategy, - expressionParser, - evaluationContextProvider); + public StringBasedR2dbcQuery(R2dbcQueryMethod queryMethod, R2dbcEntityOperations entityOperations, + R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy, ExpressionParser expressionParser, + ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { + this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, entityOperations, converter, dataAccessStrategy, + expressionParser, evaluationContextProvider); } /** @@ -73,22 +82,23 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { * {@link DatabaseClient}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}. * * @param method must not be {@literal null}. - * @param databaseClient must not be {@literal null}. + * @param entityOperations must not be {@literal null}. * @param converter must not be {@literal null}. * @param dataAccessStrategy must not be {@literal null}. * @param expressionParser must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. */ - public StringBasedR2dbcQuery(String query, R2dbcQueryMethod method, DatabaseClient databaseClient, + public StringBasedR2dbcQuery(String query, R2dbcQueryMethod method, R2dbcEntityOperations entityOperations, R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { - super(method, databaseClient, converter); + super(method, entityOperations, converter); this.expressionParser = expressionParser; this.evaluationContextProvider = evaluationContextProvider; Assert.hasText(query, "Query must not be empty"); + this.dataAccessStrategy = dataAccessStrategy; this.expressionQuery = ExpressionQuery.create(query); this.binder = new ExpressionEvaluatingParameterBinder(expressionQuery, dataAccessStrategy); this.expressionDependencies = createExpressionDependencies(); @@ -141,20 +151,8 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { * @see org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery#createQuery(org.springframework.data.relational.repository.query.RelationalParameterAccessor) */ @Override - protected Mono createQuery(RelationalParameterAccessor accessor) { - - return getSpelEvaluator(accessor).map(evaluator -> new BindableQuery() { - - @Override - public DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec) { - return binder.bind(bindSpec, accessor, evaluator); - } - - @Override - public String get() { - return expressionQuery.getQuery(); - } - }); + protected Mono> createQuery(RelationalParameterAccessor accessor) { + return getSpelEvaluator(accessor).map(evaluator -> new ExpandedQuery(accessor, evaluator)); } @Override @@ -172,4 +170,108 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { context -> new DefaultR2dbcSpELExpressionEvaluator(expressionParser, context)) .defaultIfEmpty(DefaultR2dbcSpELExpressionEvaluator.unsupported()); } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getSimpleName()); + sb.append(" [").append(expressionQuery.getQuery()); + sb.append(']'); + return sb.toString(); + } + + private class ExpandedQuery implements PreparedOperation { + + private final BindTargetRecorder recordedBindings; + + private final PreparedOperation expanded; + + private final Map remainderByName; + + private final Map remainderByIndex; + + public ExpandedQuery(RelationalParameterAccessor accessor, R2dbcSpELExpressionEvaluator evaluator) { + + this.recordedBindings = new BindTargetRecorder(); + binder.bind(recordedBindings, accessor, evaluator); + + remainderByName = new LinkedHashMap<>(recordedBindings.byName); + remainderByIndex = new LinkedHashMap<>(recordedBindings.byIndex); + expanded = dataAccessStrategy.processNamedParameters(expressionQuery.getQuery(), (index, name) -> { + + if (recordedBindings.byName.containsKey(name)) { + remainderByName.remove(name); + return SettableValue.fromParameter(recordedBindings.byName.get(name)); + } + + if (recordedBindings.byIndex.containsKey(index)) { + remainderByIndex.remove(index); + return SettableValue.fromParameter(recordedBindings.byIndex.get(index)); + } + + return null; + }); + } + + @Override + public String getSource() { + return expressionQuery.getQuery(); + } + + @Override + public void bindTo(BindTarget target) { + + expanded.bindTo(target); + + remainderByName.forEach(target::bind); + remainderByIndex.forEach(target::bind); + } + + @Override + public String toQuery() { + return expanded.toQuery(); + } + + @Override + public String toString() { + return String.format("Original: [%s], Expanded: [%s]", expressionQuery.getQuery(), expanded.toQuery()); + } + } + + private static class BindTargetRecorder implements BindTarget { + + final Map byIndex = new LinkedHashMap<>(); + + final Map byName = new LinkedHashMap<>(); + + @Override + public void bind(String identifier, Object value) { + byName.put(identifier, toParameter(value)); + } + + @NotNull + private Parameter toParameter(Object value) { + + if (value instanceof SettableValue) { + return ((SettableValue) value).toParameter(); + } + + return value instanceof Parameter ? (Parameter) value : Parameter.from(value); + } + + @Override + public void bind(int index, Object value) { + byIndex.put(index, toParameter(value)); + } + + @Override + public void bindNull(String identifier, Class type) { + byName.put(identifier, Parameter.empty(type)); + } + + @Override + public void bindNull(int index, Class type) { + byIndex.put(index, Parameter.empty(type)); + } + } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java index 02996eee..8e55eb06 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java @@ -129,7 +129,7 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport { @Override protected Optional getQueryLookupStrategy(@Nullable Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) { - return Optional.of(new R2dbcQueryLookupStrategy(this.databaseClient, + return Optional.of(new R2dbcQueryLookupStrategy(this.operations, (ReactiveQueryMethodEvaluationContextProvider) evaluationContextProvider, this.converter, this.dataAccessStrategy)); } @@ -158,16 +158,16 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport { */ private static class R2dbcQueryLookupStrategy implements QueryLookupStrategy { - private final DatabaseClient databaseClient; + private final R2dbcEntityOperations entityOperations; private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider; private final R2dbcConverter converter; private final ReactiveDataAccessStrategy dataAccessStrategy; private final ExpressionParser parser = new CachingExpressionParser(EXPRESSION_PARSER); - R2dbcQueryLookupStrategy(DatabaseClient databaseClient, + R2dbcQueryLookupStrategy(R2dbcEntityOperations entityOperations, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider, R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy) { - this.databaseClient = databaseClient; + this.entityOperations = entityOperations; this.evaluationContextProvider = evaluationContextProvider; this.converter = converter; this.dataAccessStrategy = dataAccessStrategy; @@ -188,15 +188,15 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport { if (namedQueries.hasQuery(namedQueryName)) { String namedQuery = namedQueries.getQuery(namedQueryName); - return new StringBasedR2dbcQuery(namedQuery, queryMethod, this.databaseClient, this.converter, + return new StringBasedR2dbcQuery(namedQuery, queryMethod, this.entityOperations, this.converter, this.dataAccessStrategy, parser, this.evaluationContextProvider); } else if (queryMethod.hasAnnotatedQuery()) { - return new StringBasedR2dbcQuery(queryMethod, this.databaseClient, this.converter, this.dataAccessStrategy, + return new StringBasedR2dbcQuery(queryMethod, this.entityOperations, this.converter, this.dataAccessStrategy, this.parser, this.evaluationContextProvider); } else { - return new PartTreeR2dbcQuery(queryMethod, this.databaseClient, this.converter, this.dataAccessStrategy); + return new PartTreeR2dbcQuery(queryMethod, this.entityOperations, this.converter, this.dataAccessStrategy); } } } diff --git a/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryIntegrationTests.java index be7d52c1..562650f2 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryIntegrationTests.java @@ -129,7 +129,7 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg } @Test - void shouldFindItemsByNameLike() { + void shouldFindItemsByNameContains() { shouldInsertNewItems(); diff --git a/src/test/java/org/springframework/data/r2dbc/repository/H2R2dbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/H2R2dbcRepositoryIntegrationTests.java index d40cf1e8..bfcf6358 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/H2R2dbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/H2R2dbcRepositoryIntegrationTests.java @@ -25,12 +25,16 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import javax.sql.DataSource; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.reactivestreams.Publisher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -39,10 +43,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.data.annotation.Id; import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration; +import org.springframework.data.r2dbc.mapping.event.AfterConvertCallback; import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory; import org.springframework.data.r2dbc.testing.H2TestSupport; import org.springframework.data.relational.core.mapping.Table; +import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.repository.reactive.ReactiveCrudRepository; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -59,6 +65,7 @@ public class H2R2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIn @Autowired private H2LegoSetRepository repository; @Autowired private IdOnlyEntityRepository idOnlyEntityRepository; + @Autowired private AfterConvertCallbackRecorder recorder; @Configuration @EnableR2dbcRepositories(considerNestedRepositories = true, @@ -70,6 +77,16 @@ public class H2R2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIn public ConnectionFactory connectionFactory() { return H2TestSupport.createConnectionFactory(); } + + @Bean + public AfterConvertCallbackRecorder afterConvertCallbackRecorder() { + return new AfterConvertCallbackRecorder(); + } + } + + @BeforeEach + void setUp() { + recorder.clear(); } @Override @@ -92,6 +109,18 @@ public class H2R2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIn return H2LegoSetRepository.class; } + @Test // gh-591 + void shouldFindItemsByManual() { + super.shouldFindItemsByManual(); + assertThat(recorder.seenEntities).hasSize(1); + } + + @Test // gh-591 + void shouldFindItemsByNameContains() { + super.shouldFindItemsByNameContains(); + assertThat(recorder.seenEntities).hasSize(2); + } + @Test // gh-469 void shouldSuppressNullValues() { repository.findMax("doo").as(StepVerifier::create).verifyComplete(); @@ -196,4 +225,19 @@ public class H2R2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIn static class IdOnlyEntity { @Id Integer id; } + + static class AfterConvertCallbackRecorder implements AfterConvertCallback { + + List seenEntities = new ArrayList<>(); + + @Override + public Publisher onAfterConvert(LegoSet entity, SqlIdentifier table) { + seenEntities.add(entity); + return Mono.just(entity); + } + + public void clear() { + seenEntities.clear(); + } + } } diff --git a/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java index aff625b2..8207e62d 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java @@ -42,6 +42,8 @@ import org.springframework.data.annotation.Id; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.r2dbc.convert.R2dbcConverter; import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy; +import org.springframework.data.r2dbc.core.R2dbcEntityOperations; +import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.r2dbc.dialect.DialectResolver; import org.springframework.data.r2dbc.dialect.R2dbcDialect; @@ -52,6 +54,8 @@ import org.springframework.data.relational.repository.query.RelationalParameters import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.PreparedOperation; +import org.springframework.r2dbc.core.binding.BindTarget; /** * Unit tests for {@link PartTreeR2dbcQuery}. @@ -75,7 +79,7 @@ class PartTreeR2dbcQueryUnitTests { private RelationalMappingContext mappingContext; private ReactiveDataAccessStrategy dataAccessStrategy; - private DatabaseClient databaseClient; + private R2dbcEntityOperations operations; @BeforeEach void setUp() { @@ -92,18 +96,19 @@ class PartTreeR2dbcQueryUnitTests { R2dbcDialect dialect = DialectResolver.getDialect(connectionFactory); dataAccessStrategy = new DefaultReactiveDataAccessStrategy(dialect, r2dbcConverter); - databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory).build(); + operations = new R2dbcEntityTemplate(DatabaseClient.builder().connectionFactory(connectionFactory).build(), + dataAccessStrategy); } @Test // gh-282 void createsQueryToFindAllEntitiesByStringAttribute() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery, "John"); + PreparedOperation preparedOperation = createQuery(queryMethod, r2dbcQuery, "John"); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1"); } @@ -111,11 +116,11 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryWithIsNullCondition() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery, new Object[] { null }); + PreparedOperation preparedOperation = createQuery(queryMethod, r2dbcQuery, new Object[] { null }); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name IS NULL"); } @@ -123,9 +128,9 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryWithLimitForExistsProjection() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("existsByFirstName", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); - BindableQuery query = createQuery(queryMethod, r2dbcQuery, "John"); + PreparedOperation query = createQuery(queryMethod, r2dbcQuery, "John"); assertThat(query.get()) .isEqualTo("SELECT " + TABLE + ".id FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 1"); @@ -135,11 +140,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByTwoStringAttributes() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameAndFirstName", String.class, String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = createQuery(r2dbcQuery, getAccessor(queryMethod, new Object[] { "Doe", "John" })); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, + getAccessor(queryMethod, new Object[] { "Doe", "John" })); - assertThat(bindableQuery.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + assertThat(preparedOperation.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".last_name = $1 AND (" + TABLE + ".first_name = $2)"); } @@ -147,11 +153,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByOneOfTwoStringAttributes() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameOrFirstName", String.class, String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = createQuery(r2dbcQuery, getAccessor(queryMethod, new Object[] { "Doe", "John" })); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, + getAccessor(queryMethod, new Object[] { "Doe", "John" })); - assertThat(bindableQuery.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + assertThat(preparedOperation.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".last_name = $1 OR (" + TABLE + ".first_name = $2)"); } @@ -159,34 +166,33 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByDateAttributeBetween() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthBetween", Date.class, Date.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); Date from = new Date(); Date to = new Date(); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { from, to }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth BETWEEN $1 AND $2"); - DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); - when(bindSpecMock.bind(anyInt(), any())).thenReturn(bindSpecMock); - bindableQuery.bind(bindSpecMock); + BindTarget bindTarget = mock(BindTarget.class); + preparedOperation.bindTo(bindTarget); - verify(bindSpecMock, times(1)).bind(0, from); - verify(bindSpecMock, times(1)).bind(1, to); + verify(bindTarget, times(1)).bind(0, from); + verify(bindTarget, times(1)).bind(1, to); } @Test // gh-282 void createsQueryToFindAllEntitiesByIntegerAttributeLessThan() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeLessThan", Integer.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age < $1"); } @@ -194,12 +200,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByIntegerAttributeLessThanEqual() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeLessThanEqual", Integer.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age <= $1"); } @@ -207,12 +213,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByIntegerAttributeGreaterThan() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeGreaterThan", Integer.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age > $1"); } @@ -220,12 +226,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByIntegerAttributeGreaterThanEqual() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeGreaterThanEqual", Integer.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age >= $1"); } @@ -233,24 +239,24 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByDateAttributeAfter() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthAfter", Date.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth > $1"); } @Test // gh-282 void createsQueryToFindAllEntitiesByDateAttributeBefore() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthBefore", Date.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth < $1"); } @@ -258,12 +264,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByIntegerAttributeIsNull() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIsNull"); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IS NULL"); } @@ -271,12 +277,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByIntegerAttributeIsNotNull() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIsNotNull"); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IS NOT NULL"); } @@ -284,12 +290,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByStringAttributeLike() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameLike", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1"); } @@ -297,12 +303,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByStringAttributeNotLike() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotLike", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name NOT LIKE $1"); } @@ -310,148 +316,144 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByStringAttributeStartingWith() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameStartingWith", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1"); } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Test // gh-282 void appendsLikeOperatorParameterWithPercentSymbolForStartingWithQuery() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameStartingWith", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); - DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); - bindableQuery.bind(bindSpecMock); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); + BindTarget bindTarget = mock(BindTarget.class); + preparedOperation.bindTo(bindTarget); - verify(bindSpecMock, times(1)).bind(0, "Jo%"); + verify(bindTarget, times(1)).bind(0, "Jo%"); } @Test // gh-282 void createsQueryToFindAllEntitiesByStringAttributeEndingWith() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameEndingWith", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1"); } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Test // gh-282 void prependsLikeOperatorParameterWithPercentSymbolForEndingWithQuery() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameEndingWith", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); - DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); - bindableQuery.bind(bindSpecMock); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); + BindTarget bindTarget = mock(BindTarget.class); + preparedOperation.bindTo(bindTarget); - verify(bindSpecMock, times(1)).bind(0, "%hn"); + verify(bindTarget, times(1)).bind(0, "%hn"); } @Test // gh-282 void createsQueryToFindAllEntitiesByStringAttributeContaining() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameContaining", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1"); } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Test // gh-282 void wrapsLikeOperatorParameterWithPercentSymbolsForContainingQuery() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameContaining", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); - DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); - bindableQuery.bind(bindSpecMock); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); + BindTarget bindTarget = mock(BindTarget.class); + preparedOperation.bindTo(bindTarget); - verify(bindSpecMock, times(1)).bind(0, "%oh%"); + verify(bindTarget, times(1)).bind(0, "%oh%"); } @Test // gh-282 void createsQueryToFindAllEntitiesByStringAttributeNotContaining() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotContaining", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name NOT LIKE $1"); } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Test // gh-282 void wrapsLikeOperatorParameterWithPercentSymbolsForNotContainingQuery() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotContaining", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); - DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); - bindableQuery.bind(bindSpecMock); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); + BindTarget bindTarget = mock(BindTarget.class); + preparedOperation.bindTo(bindTarget); - verify(bindSpecMock, times(1)).bind(0, "%oh%"); + verify(bindTarget, times(1)).bind(0, "%oh%"); } @Test // gh-282 void createsQueryToFindAllEntitiesByIntegerAttributeWithDescendingOrderingByStringAttribute() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeOrderByLastNameDesc", Integer.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age = $1 ORDER BY last_name DESC"); } @Test // gh-282 void createsQueryToFindAllEntitiesByIntegerAttributeWithAscendingOrderingByStringAttribute() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeOrderByLastNameAsc", Integer.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age = $1 ORDER BY last_name ASC"); } @Test // gh-282 void createsQueryToFindAllEntitiesByStringAttributeNot() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameNot", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Doe" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".last_name != $1"); } @@ -459,26 +461,26 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByIntegerAttributeIn() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIn", Collection.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { Collections.singleton(25) }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IN ($1)"); } @Test // gh-282 void createsQueryToFindAllEntitiesByIntegerAttributeNotIn() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeNotIn", Collection.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { Collections.singleton(25) }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age NOT IN ($1)"); } @@ -486,12 +488,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByBooleanAttributeTrue() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByActiveTrue"); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".active = TRUE"); } @@ -499,12 +501,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByBooleanAttributeFalse() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByActiveFalse"); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".active = FALSE"); } @@ -512,12 +514,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindAllEntitiesByStringAttributeIgnoringCase() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameIgnoreCase", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE UPPER(" + TABLE + ".first_name) = UPPER($1)"); } @@ -525,7 +527,7 @@ class PartTreeR2dbcQueryUnitTests { void throwsExceptionWhenIgnoringCaseIsImpossible() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findByIdIgnoringCase", Long.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); assertThatIllegalStateException() @@ -538,7 +540,7 @@ class PartTreeR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByIdIn", Long.class); assertThatIllegalArgumentException() - .isThrownBy(() -> new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy)); + .isThrownBy(() -> new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy)); } @Test // gh-282 @@ -547,14 +549,14 @@ class PartTreeR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = getQueryMethod("findAllById", Collection.class); assertThatIllegalArgumentException() - .isThrownBy(() -> new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy)); + .isThrownBy(() -> new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy)); } @Test // gh-282 void throwsExceptionWhenConditionKeywordIsUnsupported() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByIdIsEmpty"); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); assertThatIllegalArgumentException() @@ -565,7 +567,7 @@ class PartTreeR2dbcQueryUnitTests { void throwsExceptionWhenInvalidNumberOfParameterIsGiven() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); assertThatIllegalArgumentException() @@ -576,12 +578,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryWithLimitToFindEntitiesByStringAttribute() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findTop3ByFirstName", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 3"); } @@ -589,12 +591,12 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindFirstEntityByStringAttribute() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findFirstByFirstName", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()) + assertThat(preparedOperation.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 1"); } @@ -602,23 +604,23 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToDeleteByFirstName() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("deleteByFirstName", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); - BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor); + PreparedOperation preparedOperation = createQuery(r2dbcQuery, accessor); - assertThat(bindableQuery.get()).isEqualTo("DELETE FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1"); + assertThat(preparedOperation.get()).isEqualTo("DELETE FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1"); } @Test // gh-344 void createsQueryToFindAllEntitiesByStringAttributeWithDistinct() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findDistinctByFirstName", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery, "John"); + PreparedOperation preparedOperation = createQuery(queryMethod, r2dbcQuery, "John"); - assertThat(bindableQuery.get()).isEqualTo("SELECT " + DISTINCT + " " + TABLE + ".first_name, " + TABLE + assertThat(preparedOperation.get()).isEqualTo("SELECT " + DISTINCT + " " + TABLE + ".first_name, " + TABLE + ".foo FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1"); } @@ -626,11 +628,11 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryToFindByOpenProjection() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findOpenProjectionBy"); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery); + PreparedOperation preparedOperation = createQuery(queryMethod, r2dbcQuery); - assertThat(bindableQuery.get()).isEqualTo( + assertThat(preparedOperation.get()).isEqualTo( "SELECT users.id, users.first_name, users.last_name, users.date_of_birth, users.age, users.active FROM " + TABLE); } @@ -639,11 +641,11 @@ class PartTreeR2dbcQueryUnitTests { void createsDtoProjectionQuery() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("findAsDtoProjectionBy"); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); - BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery); + PreparedOperation preparedOperation = createQuery(queryMethod, r2dbcQuery); - assertThat(bindableQuery.get()).isEqualTo( + assertThat(preparedOperation.get()).isEqualTo( "SELECT users.id, users.first_name, users.last_name, users.date_of_birth, users.age, users.active FROM " + TABLE); } @@ -652,19 +654,21 @@ class PartTreeR2dbcQueryUnitTests { void createsQueryForCountProjection() throws Exception { R2dbcQueryMethod queryMethod = getQueryMethod("countByFirstName", String.class); - PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, + PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy); - BindableQuery query = createQuery(queryMethod, r2dbcQuery, "John"); + PreparedOperation query = createQuery(queryMethod, r2dbcQuery, "John"); assertThat(query.get()) .isEqualTo("SELECT COUNT(users.id) FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1"); } - private BindableQuery createQuery(R2dbcQueryMethod queryMethod, PartTreeR2dbcQuery r2dbcQuery, Object... parameters) { + private PreparedOperation createQuery(R2dbcQueryMethod queryMethod, PartTreeR2dbcQuery r2dbcQuery, + Object... parameters) { return createQuery(r2dbcQuery, getAccessor(queryMethod, parameters)); } - private BindableQuery createQuery(PartTreeR2dbcQuery r2dbcQuery, RelationalParametersParameterAccessor accessor) { + private PreparedOperation createQuery(PartTreeR2dbcQuery r2dbcQuery, + RelationalParametersParameterAccessor accessor) { return r2dbcQuery.createQuery(accessor).block(); } @@ -678,6 +682,7 @@ class PartTreeR2dbcQueryUnitTests { return new RelationalParametersParameterAccessor(queryMethod, values); } + @SuppressWarnings("ALL") interface UserRepository extends Repository { Flux findAllByFirstName(String firstName); diff --git a/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java index b12a737d..eb1463e9 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java @@ -16,7 +16,6 @@ package org.springframework.data.r2dbc.repository.query; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.Method; @@ -34,6 +33,7 @@ import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.r2dbc.convert.MappingR2dbcConverter; import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy; +import org.springframework.data.r2dbc.core.R2dbcEntityOperations; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.r2dbc.dialect.PostgresDialect; import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; @@ -45,8 +45,9 @@ import org.springframework.data.repository.core.support.AbstractRepositoryMetada import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.r2dbc.core.DatabaseClient; -import org.springframework.r2dbc.core.DatabaseClient.GenericExecuteSpec; +import org.springframework.r2dbc.core.Parameter; +import org.springframework.r2dbc.core.PreparedOperation; +import org.springframework.r2dbc.core.binding.BindTarget; import org.springframework.util.ReflectionUtils; /** @@ -60,8 +61,8 @@ public class StringBasedR2dbcQueryUnitTests { private static final SpelExpressionParser PARSER = new SpelExpressionParser(); - @Mock private DatabaseClient databaseClient; - @Mock private GenericExecuteSpec bindSpec; + @Mock private R2dbcEntityOperations entityOperations; + @Mock private BindTarget bindTarget; private RelationalMappingContext mappingContext; private MappingR2dbcConverter converter; @@ -77,9 +78,6 @@ public class StringBasedR2dbcQueryUnitTests { this.accessStrategy = new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE, converter); this.metadata = AbstractRepositoryMetadata.getMetadata(SampleRepository.class); this.factory = new SpelAwareProxyProjectionFactory(); - - when(bindSpec.bind(anyInt(), any())).thenReturn(bindSpec); - when(bindSpec.bind(anyString(), any())).thenReturn(bindSpec); } @Test @@ -88,12 +86,12 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("findByLastname", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind(0, "White"); + verify(bindTarget).bind(0, Parameter.from("White")); } @Test // gh-164 @@ -102,12 +100,12 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("findByLastnamePositional", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind(0, "White"); + verify(bindTarget).bind(0, Parameter.from("White")); } @Test @@ -116,12 +114,12 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("findByNamedParameter", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); - assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :lastname"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind("lastname", "White"); + verify(bindTarget).bind(0, "White"); } @Test @@ -130,12 +128,12 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("findByNamedBindMarker", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = @lastname"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind("lastname", "White"); + verify(bindTarget).bind("lastname", Parameter.from("White")); } @Test @@ -144,12 +142,13 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("findNotByNamedBindMarker", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); - assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :unknown"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + assertThat(stringQuery.getSource()).isEqualTo("SELECT * FROM person WHERE lastname = :unknown"); + assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind(0, "White"); + verify(bindTarget).bind(0, "White"); } @Test // gh-164 @@ -158,12 +157,13 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("simpleSpel"); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod()); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); - assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + assertThat(stringQuery.getSource()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__"); + assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind("__synthetic_0__", "hello"); + verify(bindTarget).bind(0, "hello"); } @Test // gh-164 @@ -172,13 +172,14 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("simpleIndexedSpel", String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White"); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); - assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + assertThat(stringQuery.getSource()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__"); + assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind("__synthetic_0__", "White"); - verifyNoMoreInteractions(bindSpec); + verify(bindTarget).bind(0, "White"); + verifyNoMoreInteractions(bindTarget); } @Test // gh-164 @@ -187,15 +188,15 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("simplePositionalSpel", String.class, String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White", "Walter"); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); - assertThat(stringQuery.get()) + assertThat(stringQuery.getSource()) .isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__ and firstname = :firstname"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind("__synthetic_0__", "White"); - verify(bindSpec).bind("firstname", "Walter"); - verifyNoMoreInteractions(bindSpec); + verify(bindTarget).bind(0, "White"); + verify(bindTarget).bind(1, "Walter"); + verifyNoMoreInteractions(bindTarget); } @Test // gh-164 @@ -204,15 +205,15 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("simpleNamedSpel", String.class, String.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White", "Walter"); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); assertThat(stringQuery.get()) - .isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__ and firstname = :firstname"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + .isEqualTo("SELECT * FROM person WHERE lastname = $1 and firstname = $2"); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind("__synthetic_0__", "White"); - verify(bindSpec).bind("firstname", "Walter"); - verifyNoMoreInteractions(bindSpec); + verify(bindTarget).bind(0, "White"); + verify(bindTarget).bind(1, "Walter"); + verifyNoMoreInteractions(bindTarget); } @Test // gh-164 @@ -221,13 +222,13 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("queryWithSpelObject", Person.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), new Person("Walter")); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); - assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind("__synthetic_0__", "Walter"); - verifyNoMoreInteractions(bindSpec); + verify(bindTarget).bind(0, "Walter"); + verifyNoMoreInteractions(bindTarget); } @Test // gh-321 @@ -236,13 +237,13 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("queryWithUnusedParameter", String.class, Sort.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "Walter", null); - BindableQuery stringQuery = query.createQuery(accessor).block(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); - assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :name"); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1"); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind(0, "Walter"); - verifyNoMoreInteractions(bindSpec); + verify(bindTarget).bind(0, "Walter"); + verifyNoMoreInteractions(bindTarget); } @Test // gh-465 @@ -251,11 +252,11 @@ public class StringBasedR2dbcQueryUnitTests { StringBasedR2dbcQuery query = getQueryMethod("queryWithEnum", MyEnum.class); R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), MyEnum.INSTANCE); - BindableQuery stringQuery = query.createQuery(accessor).block(); - assertThat(stringQuery.bind(bindSpec)).isNotNull(); + PreparedOperation stringQuery = query.createQuery(accessor).block(); + stringQuery.bindTo(bindTarget); - verify(bindSpec).bind(0, "INSTANCE"); - verifyNoMoreInteractions(bindSpec); + verify(bindTarget).bind(0, "INSTANCE"); + verifyNoMoreInteractions(bindTarget); } @Test // gh-475 @@ -280,7 +281,7 @@ public class StringBasedR2dbcQueryUnitTests { R2dbcQueryMethod queryMethod = new R2dbcQueryMethod(method, metadata, factory, converter.getMappingContext()); - return new StringBasedR2dbcQuery(queryMethod, databaseClient, converter, accessStrategy, PARSER, + return new StringBasedR2dbcQuery(queryMethod, entityOperations, converter, accessStrategy, PARSER, ReactiveQueryMethodEvaluationContextProvider.DEFAULT); }