Skip to content

Commit 03a667c

Browse files
committed
#465 - Convert bind values for String-based queries to their native type.
We now invoke the converter for query arguments of String-based queries (using the Query annotation or named queries) to convert the value into a type that can be used by the driver. Previously all values were passed-thru which caused for e.g. enums to pass-thru these to the driver and the driver encode failed.
1 parent c3cb70f commit 03a667c

File tree

4 files changed

+96
-30
lines changed

4 files changed

+96
-30
lines changed

src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.concurrent.ConcurrentHashMap;
2323
import java.util.regex.Pattern;
2424

25+
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
2526
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
2627
import org.springframework.data.repository.query.Parameter;
2728
import org.springframework.data.repository.query.Parameters;
@@ -38,15 +39,19 @@ class ExpressionEvaluatingParameterBinder {
3839

3940
private final ExpressionQuery expressionQuery;
4041

42+
private final ReactiveDataAccessStrategy dataAccessStrategy;
43+
4144
private final Map<String, Boolean> namedParameters = new ConcurrentHashMap<>();
4245

4346
/**
4447
* Creates new {@link ExpressionEvaluatingParameterBinder}
4548
*
4649
* @param expressionQuery must not be {@literal null}.
50+
* @param dataAccessStrategy must not be {@literal null}.
4751
*/
48-
ExpressionEvaluatingParameterBinder(ExpressionQuery expressionQuery) {
52+
ExpressionEvaluatingParameterBinder(ExpressionQuery expressionQuery, ReactiveDataAccessStrategy dataAccessStrategy) {
4953
this.expressionQuery = expressionQuery;
54+
this.dataAccessStrategy = dataAccessStrategy;
5055
}
5156

5257
/**
@@ -76,58 +81,87 @@ private DatabaseClient.GenericExecuteSpec bindExpressions(DatabaseClient.Generic
7681

7782
for (ParameterBinding binding : expressionQuery.getBindings()) {
7883

79-
org.springframework.r2dbc.core.Parameter valueForBinding = evaluator.evaluate(binding.getExpression());
84+
org.springframework.r2dbc.core.Parameter valueForBinding = getBindValue(
85+
evaluator.evaluate(binding.getExpression()));
8086

81-
if (valueForBinding.isEmpty()) {
82-
bindSpecToUse = bindSpecToUse.bindNull(binding.getParameterName(), valueForBinding.getType());
83-
} else {
84-
bindSpecToUse = bindSpecToUse.bind(binding.getParameterName(), valueForBinding.getValue());
85-
}
87+
bindSpecToUse = bind(bindSpecToUse, binding.getParameterName(), valueForBinding);
8688
}
8789

8890
return bindSpecToUse;
8991
}
9092

9193
private DatabaseClient.GenericExecuteSpec bindParameters(DatabaseClient.GenericExecuteSpec bindSpec,
92-
boolean bindableNull, Object[] values, Parameters<?, ?> bindableParameters) {
94+
boolean hasBindableNullValue, Object[] values, Parameters<?, ?> bindableParameters) {
9395

9496
DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec;
9597
int bindingIndex = 0;
9698

9799
for (Parameter bindableParameter : bindableParameters) {
98100

99-
Object value = values[bindableParameter.getIndex()];
100101
Optional<String> name = bindableParameter.getName();
101102

102-
if ((name.isPresent() && isNamedParameterUsed(name)) || !expressionQuery.getBindings().isEmpty()) {
103+
if (name.isPresent() && (isNamedParameterReferencedFromQuery(name)) || !expressionQuery.getBindings().isEmpty()) {
103104

104-
if (isNamedParameterUsed(name)) {
105+
if (!isNamedParameterReferencedFromQuery(name)) {
106+
continue;
107+
}
108+
109+
org.springframework.r2dbc.core.Parameter parameter = getBindValue(values, bindableParameter);
105110

106-
if (value == null) {
107-
if (bindableNull) {
108-
bindSpecToUse = bindSpecToUse.bindNull(name.get(), bindableParameter.getType());
109-
}
110-
} else {
111-
bindSpecToUse = bindSpecToUse.bind(name.get(), value);
112-
}
111+
if (!parameter.isEmpty() || hasBindableNullValue) {
112+
bindSpecToUse = bind(bindSpecToUse, name.get(), parameter);
113113
}
114114

115115
// skip unused named parameters if there is SpEL
116116
} else {
117-
if (value == null) {
118-
if (bindableNull) {
119-
bindSpecToUse = bindSpecToUse.bindNull(bindingIndex++, bindableParameter.getType());
120-
}
121-
} else {
122-
bindSpecToUse = bindSpecToUse.bind(bindingIndex++, value);
117+
118+
org.springframework.r2dbc.core.Parameter parameter = getBindValue(values, bindableParameter);
119+
120+
if (!parameter.isEmpty() || hasBindableNullValue) {
121+
bindSpecToUse = bind(bindSpecToUse, bindingIndex++, parameter);
123122
}
124123
}
125124
}
126125

127126
return bindSpecToUse;
128127
}
129128

130-
private boolean isNamedParameterUsed(Optional<String> name) {
129+
private org.springframework.r2dbc.core.Parameter getBindValue(Object[] values, Parameter bindableParameter) {
130+
131+
org.springframework.r2dbc.core.Parameter parameter = org.springframework.r2dbc.core.Parameter
132+
.fromOrEmpty(values[bindableParameter.getIndex()], bindableParameter.getType());
133+
134+
return dataAccessStrategy.getBindValue(parameter);
135+
}
136+
137+
private static DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec spec, String name,
138+
org.springframework.r2dbc.core.Parameter parameter) {
139+
140+
Object value = parameter.getValue();
141+
if (value == null) {
142+
return spec.bindNull(name, parameter.getType());
143+
} else {
144+
return spec.bind(name, value);
145+
}
146+
}
147+
148+
private static DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec spec, int index,
149+
org.springframework.r2dbc.core.Parameter parameter) {
150+
151+
Object value = parameter.getValue();
152+
if (value == null) {
153+
return spec.bindNull(index, parameter.getType());
154+
} else {
155+
156+
return spec.bind(index, value);
157+
}
158+
}
159+
160+
private org.springframework.r2dbc.core.Parameter getBindValue(org.springframework.r2dbc.core.Parameter bindValue) {
161+
return dataAccessStrategy.getBindValue(bindValue);
162+
}
163+
164+
private boolean isNamedParameterReferencedFromQuery(Optional<String> name) {
131165

132166
if (!name.isPresent()) {
133167
return false;

src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.List;
2222

2323
import org.springframework.data.r2dbc.convert.R2dbcConverter;
24+
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
2425
import org.springframework.data.r2dbc.repository.Query;
2526
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
2627
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
@@ -54,12 +55,15 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
5455
* @param queryMethod must not be {@literal null}.
5556
* @param databaseClient must not be {@literal null}.
5657
* @param converter must not be {@literal null}.
58+
* @param dataAccessStrategy must not be {@literal null}.
5759
* @param expressionParser must not be {@literal null}.
5860
* @param evaluationContextProvider must not be {@literal null}.
5961
*/
6062
public StringBasedR2dbcQuery(R2dbcQueryMethod queryMethod, DatabaseClient databaseClient, R2dbcConverter converter,
63+
ReactiveDataAccessStrategy dataAccessStrategy,
6164
ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
62-
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, databaseClient, converter, expressionParser,
65+
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, databaseClient, converter, dataAccessStrategy,
66+
expressionParser,
6367
evaluationContextProvider);
6468
}
6569

@@ -70,11 +74,12 @@ public StringBasedR2dbcQuery(R2dbcQueryMethod queryMethod, DatabaseClient databa
7074
* @param method must not be {@literal null}.
7175
* @param databaseClient must not be {@literal null}.
7276
* @param converter must not be {@literal null}.
77+
* @param dataAccessStrategy must not be {@literal null}.
7378
* @param expressionParser must not be {@literal null}.
7479
* @param evaluationContextProvider must not be {@literal null}.
7580
*/
7681
public StringBasedR2dbcQuery(String query, R2dbcQueryMethod method, DatabaseClient databaseClient,
77-
R2dbcConverter converter, ExpressionParser expressionParser,
82+
R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy, ExpressionParser expressionParser,
7883
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
7984

8085
super(method, databaseClient, converter);
@@ -84,7 +89,7 @@ public StringBasedR2dbcQuery(String query, R2dbcQueryMethod method, DatabaseClie
8489
Assert.hasText(query, "Query must not be empty");
8590

8691
this.expressionQuery = ExpressionQuery.create(query);
87-
this.binder = new ExpressionEvaluatingParameterBinder(expressionQuery);
92+
this.binder = new ExpressionEvaluatingParameterBinder(expressionQuery, dataAccessStrategy);
8893
this.expressionDependencies = createExpressionDependencies();
8994
}
9095

src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,11 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
189189
if (namedQueries.hasQuery(namedQueryName)) {
190190
String namedQuery = namedQueries.getQuery(namedQueryName);
191191
return new StringBasedR2dbcQuery(namedQuery, queryMethod, this.databaseClient, this.converter,
192+
this.dataAccessStrategy,
192193
parser, this.evaluationContextProvider);
193194
} else if (queryMethod.hasAnnotatedQuery()) {
194-
return new StringBasedR2dbcQuery(queryMethod, this.databaseClient, this.converter, parser,
195+
return new StringBasedR2dbcQuery(queryMethod, this.databaseClient, this.converter, this.dataAccessStrategy,
196+
this.parser,
195197
this.evaluationContextProvider);
196198
} else {
197199
return new PartTreeR2dbcQuery(queryMethod, this.databaseClient, this.converter, this.dataAccessStrategy);

src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
import org.springframework.data.projection.ProjectionFactory;
3434
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
3535
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
36+
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
37+
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
38+
import org.springframework.data.r2dbc.dialect.PostgresDialect;
3639
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
3740
import org.springframework.data.r2dbc.repository.Query;
3841
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -62,6 +65,7 @@ public class StringBasedR2dbcQueryUnitTests {
6265

6366
private RelationalMappingContext mappingContext;
6467
private MappingR2dbcConverter converter;
68+
private ReactiveDataAccessStrategy accessStrategy;
6569
private ProjectionFactory factory;
6670
private RepositoryMetadata metadata;
6771

@@ -70,6 +74,7 @@ void setUp() {
7074

7175
this.mappingContext = new R2dbcMappingContext();
7276
this.converter = new MappingR2dbcConverter(this.mappingContext);
77+
this.accessStrategy = new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE, converter);
7378
this.metadata = AbstractRepositoryMetadata.getMetadata(SampleRepository.class);
7479
this.factory = new SpelAwareProxyProjectionFactory();
7580

@@ -240,13 +245,26 @@ void skipsNonBindableParameters() {
240245
verifyNoMoreInteractions(bindSpec);
241246
}
242247

248+
@Test // gh-465
249+
void translatesEnumToDatabaseValue() {
250+
251+
StringBasedR2dbcQuery query = getQueryMethod("queryWithEnum", MyEnum.class);
252+
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), MyEnum.INSTANCE);
253+
254+
BindableQuery stringQuery = query.createQuery(accessor).block();
255+
assertThat(stringQuery.bind(bindSpec)).isNotNull();
256+
257+
verify(bindSpec).bind(0, "INSTANCE");
258+
verifyNoMoreInteractions(bindSpec);
259+
}
260+
243261
private StringBasedR2dbcQuery getQueryMethod(String name, Class<?>... args) {
244262

245263
Method method = ReflectionUtils.findMethod(SampleRepository.class, name, args);
246264

247265
R2dbcQueryMethod queryMethod = new R2dbcQueryMethod(method, metadata, factory, converter.getMappingContext());
248266

249-
return new StringBasedR2dbcQuery(queryMethod, databaseClient, converter, PARSER,
267+
return new StringBasedR2dbcQuery(queryMethod, databaseClient, converter, accessStrategy, PARSER,
250268
ReactiveQueryMethodEvaluationContextProvider.DEFAULT);
251269
}
252270

@@ -285,6 +303,9 @@ private interface SampleRepository extends Repository<Person, String> {
285303

286304
@Query("SELECT * FROM person WHERE lastname = :name")
287305
Person queryWithUnusedParameter(String name, Sort unused);
306+
307+
@Query("SELECT * FROM person WHERE lastname = :name")
308+
Person queryWithEnum(MyEnum myEnum);
288309
}
289310

290311
static class Person {
@@ -299,4 +320,8 @@ public String getName() {
299320
return name;
300321
}
301322
}
323+
324+
enum MyEnum {
325+
INSTANCE;
326+
}
302327
}

0 commit comments

Comments
 (0)