Skip to content

Commit ba4e49a

Browse files
committed
Apply custom converter for Collection-like values in queries.
We now apply converters only for Collection-like values and no longer to Iterable types. Closes #819
1 parent ad4d4b0 commit ba4e49a

File tree

2 files changed

+75
-26
lines changed

2 files changed

+75
-26
lines changed

src/main/java/org/springframework/data/r2dbc/query/QueryMapper.java

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -348,43 +348,44 @@ private Condition mapCondition(CriteriaDefinition criteria, MutableBindings bind
348348
Object mappedValue;
349349
Class<?> typeHint;
350350

351+
Comparator comparator = criteria.getComparator();
351352
if (criteria.getValue() instanceof SettableValue) {
352353

353354
SettableValue settableValue = (SettableValue) criteria.getValue();
354355

355-
mappedValue = convertValue(settableValue.getValue(), propertyField.getTypeHint());
356+
mappedValue = convertValue(comparator, settableValue.getValue(), propertyField.getTypeHint());
356357
typeHint = getTypeHint(mappedValue, actualType.getType(), settableValue);
357358
} else if (criteria.getValue() instanceof Parameter) {
358359

359360
Parameter parameter = (Parameter) criteria.getValue();
360361

361-
mappedValue = convertValue(parameter.getValue(), propertyField.getTypeHint());
362+
mappedValue = convertValue(comparator, parameter.getValue(), propertyField.getTypeHint());
362363
typeHint = getTypeHint(mappedValue, actualType.getType(), parameter);
363364
} else if (criteria.getValue() instanceof ValueFunction) {
364365

365366
ValueFunction<Object> valueFunction = (ValueFunction<Object>) criteria.getValue();
366-
Object value = valueFunction.apply(getEscaper(criteria.getComparator()));
367+
Object value = valueFunction.apply(getEscaper(comparator));
367368

368-
mappedValue = convertValue(value, propertyField.getTypeHint());
369+
mappedValue = convertValue(comparator, value, propertyField.getTypeHint());
369370
typeHint = actualType.getType();
370371
} else {
371372

372373
Object value = criteria.getValue();
373374

374375
// Translate bind values for comparators that are bound as value but don't include a value.
375376
if (value == null) {
376-
if (criteria.getComparator() == Comparator.IS_TRUE) {
377+
if (comparator == Comparator.IS_TRUE) {
377378
value = true;
378-
} else if (criteria.getComparator() == Comparator.IS_FALSE) {
379+
} else if (comparator == Comparator.IS_FALSE) {
379380
value = false;
380381
}
381382
}
382383

383-
mappedValue = convertValue(value, propertyField.getTypeHint());
384+
mappedValue = convertValue(comparator, value, propertyField.getTypeHint());
384385
typeHint = actualType.getType();
385386
}
386387

387-
return createCondition(column, mappedValue, typeHint, bindings, criteria.getComparator(), criteria.isIgnoreCase());
388+
return createCondition(column, mappedValue, typeHint, bindings, comparator, criteria.isIgnoreCase());
388389
}
389390

390391
private Escaper getEscaper(Comparator comparator) {
@@ -427,6 +428,24 @@ public Parameter getBindValue(Parameter value) {
427428
return Parameter.from(convertValue(value.getValue(), ClassTypeInformation.OBJECT));
428429
}
429430

431+
@Nullable
432+
private Object convertValue(@Nullable Comparator comparator, @Nullable Object value, TypeInformation<?> typeHint) {
433+
434+
if (Comparator.IN.equals(comparator) && value instanceof Collection<?> && !((Collection<?>) value).isEmpty()) {
435+
436+
Collection<?> collection = (Collection<?>) value;
437+
Collection<Object> mapped = new ArrayList<>(collection.size());
438+
439+
for (Object o : collection) {
440+
mapped.add(convertValue(o, typeHint));
441+
}
442+
443+
return mapped;
444+
}
445+
446+
return convertValue(value, typeHint);
447+
}
448+
430449
@Nullable
431450
protected Object convertValue(@Nullable Object value, TypeInformation<?> typeInformation) {
432451

@@ -449,23 +468,6 @@ protected Object convertValue(@Nullable Object value, TypeInformation<?> typeInf
449468
return Pair.of(first, second);
450469
}
451470

452-
if (value instanceof Iterable) {
453-
454-
List<Object> mapped = new ArrayList<>();
455-
456-
for (Object o : (Iterable<?>) value) {
457-
mapped.add(convertValue(o, typeInformation.getActualType() != null ? typeInformation.getRequiredActualType()
458-
: ClassTypeInformation.OBJECT));
459-
}
460-
461-
return mapped;
462-
}
463-
464-
if (value.getClass().isArray()
465-
&& (ClassTypeInformation.OBJECT.equals(typeInformation) || typeInformation.isCollectionLike())) {
466-
return value;
467-
}
468-
469471
return this.converter.writeValue(value, typeInformation);
470472
}
471473

src/test/java/org/springframework/data/r2dbc/query/QueryMapperUnitTests.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import org.junit.jupiter.api.Test;
2525

26+
import org.springframework.core.convert.converter.Converter;
2627
import org.springframework.data.domain.Sort;
2728
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
2829
import org.springframework.data.r2dbc.convert.R2dbcConverter;
@@ -40,6 +41,9 @@
4041
import org.springframework.data.relational.core.sql.Functions;
4142
import org.springframework.data.relational.core.sql.Table;
4243

44+
import org.testcontainers.shaded.com.fasterxml.jackson.databind.JsonNode;
45+
import org.testcontainers.shaded.com.fasterxml.jackson.databind.node.TextNode;
46+
4347
/**
4448
* Unit tests for {@link QueryMapper}.
4549
*
@@ -53,7 +57,8 @@ class QueryMapperUnitTests {
5357

5458
QueryMapper createMapper(R2dbcDialect dialect) {
5559

56-
R2dbcCustomConversions conversions = R2dbcCustomConversions.of(dialect);
60+
R2dbcCustomConversions conversions = R2dbcCustomConversions.of(dialect, JsonNodeToStringConverter.INSTANCE,
61+
StringToJsonNodeConverter.INSTANCE);
5762

5863
R2dbcMappingContext context = new R2dbcMappingContext();
5964
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
@@ -463,6 +468,28 @@ void shouldMapAndConvertBooleanConditionProperly() {
463468
assertThat(bindings.getBindings().iterator().next().getValue()).isEqualTo((byte) 1);
464469
}
465470

471+
@Test // gh-1452
472+
void shouldMapJsonNodeToString() {
473+
474+
Criteria criteria = Criteria.where("jsonNode").is(new TextNode("foo"));
475+
476+
BoundCondition bindings = map(criteria);
477+
478+
assertThat(bindings.getCondition()).hasToString("person.json_node = ?[$1]");
479+
assertThat(bindings.getBindings().iterator().next().getValue()).isEqualTo("foo");
480+
}
481+
482+
@Test // gh-1452
483+
void shouldMapJsonNodeListToString() {
484+
485+
Criteria criteria = Criteria.where("jsonNode").in(new TextNode("foo"), new TextNode("bar"));
486+
487+
BoundCondition bindings = map(criteria);
488+
489+
assertThat(bindings.getCondition()).hasToString("person.json_node IN (?[$1], ?[$2])");
490+
assertThat(bindings.getBindings().iterator().next().getValue()).isEqualTo("foo");
491+
}
492+
466493
private BoundCondition map(Criteria criteria) {
467494

468495
BindMarkersFactory markers = BindMarkersFactory.indexed("$", 1);
@@ -478,9 +505,29 @@ static class Person {
478505
MyEnum enumValue;
479506

480507
boolean state;
508+
509+
JsonNode jsonNode;
481510
}
482511

483512
enum MyEnum {
484513
ONE, TWO,
485514
}
515+
516+
enum JsonNodeToStringConverter implements Converter<JsonNode, String> {
517+
INSTANCE;
518+
519+
@Override
520+
public String convert(JsonNode source) {
521+
return source.asText();
522+
}
523+
}
524+
525+
enum StringToJsonNodeConverter implements Converter<String, JsonNode> {
526+
INSTANCE;
527+
528+
@Override
529+
public JsonNode convert(String source) {
530+
return new TextNode(source);
531+
}
532+
}
486533
}

0 commit comments

Comments
 (0)