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
This commit is contained in:
Mark Paluch
2023-03-15 16:45:19 +01:00
parent ad4d4b0fe2
commit ba4e49a297
2 changed files with 75 additions and 26 deletions

View File

@@ -348,24 +348,25 @@ public class QueryMapper {
Object mappedValue;
Class<?> typeHint;
Comparator comparator = criteria.getComparator();
if (criteria.getValue() instanceof SettableValue) {
SettableValue settableValue = (SettableValue) criteria.getValue();
mappedValue = convertValue(settableValue.getValue(), propertyField.getTypeHint());
mappedValue = convertValue(comparator, settableValue.getValue(), propertyField.getTypeHint());
typeHint = getTypeHint(mappedValue, actualType.getType(), settableValue);
} else if (criteria.getValue() instanceof Parameter) {
Parameter parameter = (Parameter) criteria.getValue();
mappedValue = convertValue(parameter.getValue(), propertyField.getTypeHint());
mappedValue = convertValue(comparator, parameter.getValue(), propertyField.getTypeHint());
typeHint = getTypeHint(mappedValue, actualType.getType(), parameter);
} else if (criteria.getValue() instanceof ValueFunction) {
ValueFunction<Object> valueFunction = (ValueFunction<Object>) criteria.getValue();
Object value = valueFunction.apply(getEscaper(criteria.getComparator()));
Object value = valueFunction.apply(getEscaper(comparator));
mappedValue = convertValue(value, propertyField.getTypeHint());
mappedValue = convertValue(comparator, value, propertyField.getTypeHint());
typeHint = actualType.getType();
} else {
@@ -373,18 +374,18 @@ public class QueryMapper {
// Translate bind values for comparators that are bound as value but don't include a value.
if (value == null) {
if (criteria.getComparator() == Comparator.IS_TRUE) {
if (comparator == Comparator.IS_TRUE) {
value = true;
} else if (criteria.getComparator() == Comparator.IS_FALSE) {
} else if (comparator == Comparator.IS_FALSE) {
value = false;
}
}
mappedValue = convertValue(value, propertyField.getTypeHint());
mappedValue = convertValue(comparator, value, propertyField.getTypeHint());
typeHint = actualType.getType();
}
return createCondition(column, mappedValue, typeHint, bindings, criteria.getComparator(), criteria.isIgnoreCase());
return createCondition(column, mappedValue, typeHint, bindings, comparator, criteria.isIgnoreCase());
}
private Escaper getEscaper(Comparator comparator) {
@@ -427,6 +428,24 @@ public class QueryMapper {
return Parameter.from(convertValue(value.getValue(), ClassTypeInformation.OBJECT));
}
@Nullable
private Object convertValue(@Nullable Comparator comparator, @Nullable Object value, TypeInformation<?> typeHint) {
if (Comparator.IN.equals(comparator) && value instanceof Collection<?> && !((Collection<?>) value).isEmpty()) {
Collection<?> collection = (Collection<?>) value;
Collection<Object> mapped = new ArrayList<>(collection.size());
for (Object o : collection) {
mapped.add(convertValue(o, typeHint));
}
return mapped;
}
return convertValue(value, typeHint);
}
@Nullable
protected Object convertValue(@Nullable Object value, TypeInformation<?> typeInformation) {
@@ -449,23 +468,6 @@ public class QueryMapper {
return Pair.of(first, second);
}
if (value instanceof Iterable) {
List<Object> mapped = new ArrayList<>();
for (Object o : (Iterable<?>) value) {
mapped.add(convertValue(o, typeInformation.getActualType() != null ? typeInformation.getRequiredActualType()
: ClassTypeInformation.OBJECT));
}
return mapped;
}
if (value.getClass().isArray()
&& (ClassTypeInformation.OBJECT.equals(typeInformation) || typeInformation.isCollectionLike())) {
return value;
}
return this.converter.writeValue(value, typeInformation);
}

View File

@@ -23,6 +23,7 @@ import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
@@ -40,6 +41,9 @@ import org.springframework.data.relational.core.sql.Expression;
import org.springframework.data.relational.core.sql.Functions;
import org.springframework.data.relational.core.sql.Table;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.JsonNode;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.node.TextNode;
/**
* Unit tests for {@link QueryMapper}.
*
@@ -53,7 +57,8 @@ class QueryMapperUnitTests {
QueryMapper createMapper(R2dbcDialect dialect) {
R2dbcCustomConversions conversions = R2dbcCustomConversions.of(dialect);
R2dbcCustomConversions conversions = R2dbcCustomConversions.of(dialect, JsonNodeToStringConverter.INSTANCE,
StringToJsonNodeConverter.INSTANCE);
R2dbcMappingContext context = new R2dbcMappingContext();
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
@@ -463,6 +468,28 @@ class QueryMapperUnitTests {
assertThat(bindings.getBindings().iterator().next().getValue()).isEqualTo((byte) 1);
}
@Test // gh-1452
void shouldMapJsonNodeToString() {
Criteria criteria = Criteria.where("jsonNode").is(new TextNode("foo"));
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition()).hasToString("person.json_node = ?[$1]");
assertThat(bindings.getBindings().iterator().next().getValue()).isEqualTo("foo");
}
@Test // gh-1452
void shouldMapJsonNodeListToString() {
Criteria criteria = Criteria.where("jsonNode").in(new TextNode("foo"), new TextNode("bar"));
BoundCondition bindings = map(criteria);
assertThat(bindings.getCondition()).hasToString("person.json_node IN (?[$1], ?[$2])");
assertThat(bindings.getBindings().iterator().next().getValue()).isEqualTo("foo");
}
private BoundCondition map(Criteria criteria) {
BindMarkersFactory markers = BindMarkersFactory.indexed("$", 1);
@@ -478,9 +505,29 @@ class QueryMapperUnitTests {
MyEnum enumValue;
boolean state;
JsonNode jsonNode;
}
enum MyEnum {
ONE, TWO,
}
enum JsonNodeToStringConverter implements Converter<JsonNode, String> {
INSTANCE;
@Override
public String convert(JsonNode source) {
return source.asText();
}
}
enum StringToJsonNodeConverter implements Converter<String, JsonNode> {
INSTANCE;
@Override
public JsonNode convert(String source) {
return new TextNode(source);
}
}
}