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