Browse Source

Correctly convert enum array values.

We now correctly convert array write values. Previously, enum arrays were converted to null as these fell through the entity conversion.

Closes #1593
pull/1619/head
Mark Paluch 2 years ago
parent
commit
f71b41fd2c
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 38
      spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/PostgresReactiveDataAccessStrategyTests.java
  2. 75
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java
  3. 36
      spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/BasicRelationalConverterUnitTests.java

38
spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/PostgresReactiveDataAccessStrategyTests.java

@ -15,15 +15,10 @@
*/ */
package org.springframework.data.r2dbc.core; package org.springframework.data.r2dbc.core;
import static org.mockito.Mockito.*;
import static org.springframework.data.r2dbc.testing.Assertions.*;
import io.r2dbc.postgresql.codec.Interval; import io.r2dbc.postgresql.codec.Interval;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.r2dbc.convert.EnumWriteSupport;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
@ -33,7 +28,18 @@ import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static org.springframework.data.r2dbc.testing.Assertions.*; import org.junit.jupiter.api.Test;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.r2dbc.convert.EnumWriteSupport;
import org.springframework.data.r2dbc.core.StatementMapper.InsertSpec;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.r2dbc.core.Parameter;
import org.springframework.r2dbc.core.PreparedOperation;
import org.springframework.r2dbc.core.binding.BindTarget;
/** /**
* {@link PostgresDialect} specific tests for {@link ReactiveDataAccessStrategy}. * {@link PostgresDialect} specific tests for {@link ReactiveDataAccessStrategy}.
@ -58,6 +64,20 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS
assertThat(row).withColumn("myarray").hasValueInstanceOf(Integer[][].class); assertThat(row).withColumn("myarray").hasValueInstanceOf(Integer[][].class);
} }
@Test // GH-1593
void shouldConvertEnumsCorrectly() {
StatementMapper mapper = strategy.getStatementMapper();
MyEnum[] value = { MyEnum.ONE };
InsertSpec insert = mapper.createInsert("table").withColumn("my_col", Parameter.from(value));
PreparedOperation<?> mappedObject = mapper.getMappedObject(insert);
BindTarget bindTarget = mock(BindTarget.class);
mappedObject.bindTo(bindTarget);
verify(bindTarget).bind(0, new String[] { "ONE" });
}
@Test // gh-161 @Test // gh-161
void shouldConvertNullArrayToDriverArrayType() { void shouldConvertNullArrayToDriverArrayType() {

75
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java

@ -15,6 +15,7 @@
*/ */
package org.springframework.data.relational.core.conversion; package org.springframework.data.relational.core.conversion;
import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -161,55 +162,81 @@ public class BasicRelationalConverter implements RelationalConverter {
if (getConversions().isSimpleType(value.getClass())) { if (getConversions().isSimpleType(value.getClass())) {
if (TypeInformation.OBJECT != type) { if (TypeInformation.OBJECT != type && conversionService.canConvert(value.getClass(), type.getType())) {
if (conversionService.canConvert(value.getClass(), type.getType())) {
value = conversionService.convert(value, type.getType()); value = conversionService.convert(value, type.getType());
} }
}
return getPotentiallyConvertedSimpleWrite(value); return getPotentiallyConvertedSimpleWrite(value);
} }
// TODO: We should add conversion support for arrays, however, if (value.getClass().isArray()) {
// these should consider multi-dimensional arrays as well. return writeArray(value, type);
if (value.getClass().isArray() //
&& !value.getClass().getComponentType().isEnum() //
&& (TypeInformation.OBJECT.equals(type) //
|| type.isCollectionLike()) //
) {
return value;
} }
if (value instanceof Collection<?>) { if (value instanceof Collection<?>) {
return writeCollection((Iterable<?>) value, type);
}
List<Object> mapped = new ArrayList<>(); RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(value.getClass());
if (persistentEntity != null) {
Object id = persistentEntity.getIdentifierAccessor(value).getIdentifier();
return writeValue(id, type);
}
return conversionService.convert(value, type.getType());
}
private Object writeArray(Object value, TypeInformation<?> type) {
Class<?> componentType = value.getClass().getComponentType();
Optional<Class<?>> optionalWriteTarget = getConversions().getCustomWriteTarget(componentType);
if (optionalWriteTarget.isEmpty() && !componentType.isEnum()) {
return value;
}
Class<?> customWriteTarget = optionalWriteTarget
.orElseGet(() -> componentType.isEnum() ? String.class : componentType);
// optimization: bypass identity conversion
if (customWriteTarget.equals(componentType)) {
return value;
}
TypeInformation<?> component = TypeInformation.OBJECT; TypeInformation<?> component = TypeInformation.OBJECT;
if (type.isCollectionLike() && type.getActualType() != null) { if (type.isCollectionLike() && type.getActualType() != null) {
component = type.getRequiredComponentType(); component = type.getRequiredComponentType();
} }
for (Object o : (Iterable<?>) value) { int length = Array.getLength(value);
mapped.add(writeValue(o, component)); Object target = Array.newInstance(customWriteTarget, length);
for (int i = 0; i < length; i++) {
Array.set(target, i, writeValue(Array.get(value, i), component));
} }
if (type.getType().isInstance(mapped) || !type.isCollectionLike()) { return target;
return mapped;
} }
return conversionService.convert(mapped, type.getType()); private Object writeCollection(Iterable<?> value, TypeInformation<?> type) {
}
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(value.getClass()); List<Object> mapped = new ArrayList<>();
if (persistentEntity != null) { TypeInformation<?> component = TypeInformation.OBJECT;
if (type.isCollectionLike() && type.getActualType() != null) {
component = type.getRequiredComponentType();
}
Object id = persistentEntity.getIdentifierAccessor(value).getIdentifier(); for (Object o : value) {
return writeValue(id, type); mapped.add(writeValue(o, component));
} }
return conversionService.convert(value, type.getType()); if (type.getType().isInstance(mapped) || !type.isCollectionLike()) {
return mapped;
}
return conversionService.convert(mapped, type.getType());
} }
@Override @Override

36
spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/BasicRelationalConverterUnitTests.java

@ -17,14 +17,15 @@ package org.springframework.data.relational.core.conversion;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.function.Function;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.data.convert.ConverterBuilder; import org.springframework.data.convert.ConverterBuilder;
import org.springframework.data.convert.ConverterBuilder.ConverterAware;
import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@ -46,8 +47,13 @@ class BasicRelationalConverterUnitTests {
@BeforeEach @BeforeEach
public void before() throws Exception { public void before() throws Exception {
Set<GenericConverter> converters = ConverterBuilder.writing(MyValue.class, String.class, MyValue::foo) List<Object> converters = new ArrayList<>();
.andReading(MyValue::new).getConverters(); converters.addAll(
ConverterBuilder.writing(MyValue.class, String.class, MyValue::foo).andReading(MyValue::new).getConverters());
ConverterAware converterAware = ConverterBuilder
.writing(MySimpleEnum.class, MySimpleEnum.class, Function.identity()).andReading(mySimpleEnum -> mySimpleEnum);
converters.addAll(converterAware.getConverters());
CustomConversions conversions = new CustomConversions(CustomConversions.StoreConversions.NONE, converters); CustomConversions conversions = new CustomConversions(CustomConversions.StoreConversions.NONE, converters);
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
@ -79,7 +85,23 @@ class BasicRelationalConverterUnitTests {
assertThat(result).isEqualTo("ON"); assertThat(result).isEqualTo("ON");
} }
@Test // DATAJDBC-235 @Test
void shouldConvertEnumArrayToStringArray() {
Object result = converter.writeValue(new MyEnum[] { MyEnum.ON }, TypeInformation.OBJECT);
assertThat(result).isEqualTo(new String[] { "ON" });
}
@Test // GH-1593
void shouldRetainEnumArray() {
Object result = converter.writeValue(new MySimpleEnum[] { MySimpleEnum.ON }, TypeInformation.OBJECT);
assertThat(result).isEqualTo(new MySimpleEnum[] { MySimpleEnum.ON });
}
@Test // GH-1593
void shouldConvertStringToEnum() { void shouldConvertStringToEnum() {
Object result = converter.readValue("OFF", TypeInformation.of(MyEnum.class)); Object result = converter.readValue("OFF", TypeInformation.of(MyEnum.class));
@ -145,4 +167,8 @@ class BasicRelationalConverterUnitTests {
enum MyEnum { enum MyEnum {
ON, OFF; ON, OFF;
} }
enum MySimpleEnum {
ON, OFF;
}
} }

Loading…
Cancel
Save