Skip to content

Commit b522ad5

Browse files
committed
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
1 parent e1ecb4b commit b522ad5

File tree

3 files changed

+108
-37
lines changed

3 files changed

+108
-37
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.r2dbc.core;
1717

18+
import static org.mockito.Mockito.*;
1819
import static org.springframework.data.r2dbc.testing.Assertions.*;
1920

2021
import io.r2dbc.postgresql.codec.Interval;
@@ -33,9 +34,13 @@
3334
import org.springframework.data.convert.ReadingConverter;
3435
import org.springframework.data.convert.WritingConverter;
3536
import org.springframework.data.r2dbc.convert.EnumWriteSupport;
37+
import org.springframework.data.r2dbc.core.StatementMapper.InsertSpec;
3638
import org.springframework.data.r2dbc.dialect.PostgresDialect;
3739
import org.springframework.data.r2dbc.mapping.OutboundRow;
3840
import org.springframework.data.relational.core.sql.SqlIdentifier;
41+
import org.springframework.r2dbc.core.Parameter;
42+
import org.springframework.r2dbc.core.PreparedOperation;
43+
import org.springframework.r2dbc.core.binding.BindTarget;
3944

4045
/**
4146
* {@link PostgresDialect} specific tests for {@link ReactiveDataAccessStrategy}.
@@ -60,6 +65,20 @@ void shouldConvertPrimitiveMultidimensionArrayToWrapper() {
6065
assertThat(row).withColumn("myarray").hasValueInstanceOf(Integer[][].class);
6166
}
6267

68+
@Test // GH-1593
69+
void shouldConvertEnumsCorrectly() {
70+
71+
StatementMapper mapper = strategy.getStatementMapper();
72+
MyEnum[] value = { MyEnum.ONE };
73+
InsertSpec insert = mapper.createInsert("table").withColumn("my_col", Parameter.from(value));
74+
PreparedOperation<?> mappedObject = mapper.getMappedObject(insert);
75+
76+
BindTarget bindTarget = mock(BindTarget.class);
77+
mappedObject.bindTo(bindTarget);
78+
79+
verify(bindTarget).bind(0, new String[] { "ONE" });
80+
}
81+
6382
@Test // gh-161
6483
void shouldConvertNullArrayToDriverArrayType() {
6584

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

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.relational.core.conversion;
1717

18+
import java.lang.reflect.Array;
1819
import java.util.ArrayList;
1920
import java.util.Collection;
2021
import java.util.Collections;
@@ -164,44 +165,19 @@ public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
164165

165166
if (getConversions().isSimpleType(value.getClass())) {
166167

167-
if (TypeInformation.OBJECT != type) {
168-
169-
if (conversionService.canConvert(value.getClass(), type.getType())) {
170-
value = conversionService.convert(value, type.getType());
171-
}
168+
if (TypeInformation.OBJECT != type && conversionService.canConvert(value.getClass(), type.getType())) {
169+
value = conversionService.convert(value, type.getType());
172170
}
173171

174172
return getPotentiallyConvertedSimpleWrite(value);
175173
}
176174

177-
// TODO: We should add conversion support for arrays, however,
178-
// these should consider multi-dimensional arrays as well.
179-
if (value.getClass().isArray() //
180-
&& !value.getClass().getComponentType().isEnum() //
181-
&& (TypeInformation.OBJECT.equals(type) //
182-
|| type.isCollectionLike()) //
183-
) {
184-
return value;
175+
if (value.getClass().isArray()) {
176+
return writeArray(value, type);
185177
}
186178

187179
if (value instanceof Collection<?>) {
188-
189-
List<Object> mapped = new ArrayList<>();
190-
191-
TypeInformation<?> component = TypeInformation.OBJECT;
192-
if (type.isCollectionLike() && type.getActualType() != null) {
193-
component = type.getRequiredComponentType();
194-
}
195-
196-
for (Object o : (Iterable<?>) value) {
197-
mapped.add(writeValue(o, component));
198-
}
199-
200-
if (type.getType().isInstance(mapped) || !type.isCollectionLike()) {
201-
return mapped;
202-
}
203-
204-
return conversionService.convert(mapped, type.getType());
180+
return writeCollection((Iterable<?>) value, type);
205181
}
206182

207183
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(value.getClass());
@@ -215,6 +191,57 @@ public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
215191
return conversionService.convert(value, type.getType());
216192
}
217193

194+
private Object writeArray(Object value, TypeInformation<?> type) {
195+
196+
Class<?> componentType = value.getClass().getComponentType();
197+
Optional<Class<?>> optionalWriteTarget = getConversions().getCustomWriteTarget(componentType);
198+
199+
if (optionalWriteTarget.isEmpty() && !componentType.isEnum()) {
200+
return value;
201+
}
202+
203+
Class<?> customWriteTarget = optionalWriteTarget
204+
.orElseGet(() -> componentType.isEnum() ? String.class : componentType);
205+
206+
// optimization: bypass identity conversion
207+
if (customWriteTarget.equals(componentType)) {
208+
return value;
209+
}
210+
211+
TypeInformation<?> component = TypeInformation.OBJECT;
212+
if (type.isCollectionLike() && type.getActualType() != null) {
213+
component = type.getRequiredComponentType();
214+
}
215+
216+
int length = Array.getLength(value);
217+
Object target = Array.newInstance(customWriteTarget, length);
218+
for (int i = 0; i < length; i++) {
219+
Array.set(target, i, writeValue(Array.get(value, i), component));
220+
}
221+
222+
return target;
223+
}
224+
225+
private Object writeCollection(Iterable<?> value, TypeInformation<?> type) {
226+
227+
List<Object> mapped = new ArrayList<>();
228+
229+
TypeInformation<?> component = TypeInformation.OBJECT;
230+
if (type.isCollectionLike() && type.getActualType() != null) {
231+
component = type.getRequiredComponentType();
232+
}
233+
234+
for (Object o : value) {
235+
mapped.add(writeValue(o, component));
236+
}
237+
238+
if (type.getType().isInstance(mapped) || !type.isCollectionLike()) {
239+
return mapped;
240+
}
241+
242+
return conversionService.convert(mapped, type.getType());
243+
}
244+
218245
@Override
219246
public EntityInstantiators getEntityInstantiators() {
220247
return this.entityInstantiators;

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

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@
2020
import lombok.Data;
2121
import lombok.Value;
2222

23+
import java.util.ArrayList;
2324
import java.util.Arrays;
2425
import java.util.List;
25-
import java.util.Set;
26+
import java.util.function.Function;
2627

2728
import org.junit.jupiter.api.BeforeEach;
2829
import org.junit.jupiter.api.Test;
29-
import org.springframework.core.convert.converter.GenericConverter;
3030
import org.springframework.data.convert.ConverterBuilder;
31+
import org.springframework.data.convert.ConverterBuilder.ConverterAware;
3132
import org.springframework.data.convert.CustomConversions;
3233
import org.springframework.data.mapping.PersistentPropertyAccessor;
3334
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -49,8 +50,13 @@ class BasicRelationalConverterUnitTests {
4950
@BeforeEach
5051
public void before() throws Exception {
5152

52-
Set<GenericConverter> converters = ConverterBuilder.writing(MyValue.class, String.class, MyValue::getFoo)
53-
.andReading(MyValue::new).getConverters();
53+
List<Object> converters = new ArrayList<>();
54+
converters.addAll(ConverterBuilder.writing(MyValue.class, String.class, MyValue::getFoo).andReading(MyValue::new)
55+
.getConverters());
56+
57+
ConverterAware converterAware = ConverterBuilder
58+
.writing(MySimpleEnum.class, MySimpleEnum.class, Function.identity()).andReading(mySimpleEnum -> mySimpleEnum);
59+
converters.addAll(converterAware.getConverters());
5460

5561
CustomConversions conversions = new CustomConversions(CustomConversions.StoreConversions.NONE, converters);
5662
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
@@ -82,7 +88,23 @@ void shouldConvertEnumToString() {
8288
assertThat(result).isEqualTo("ON");
8389
}
8490

85-
@Test // DATAJDBC-235
91+
@Test
92+
void shouldConvertEnumArrayToStringArray() {
93+
94+
Object result = converter.writeValue(new MyEnum[] { MyEnum.ON }, TypeInformation.OBJECT);
95+
96+
assertThat(result).isEqualTo(new String[] { "ON" });
97+
}
98+
99+
@Test // GH-1593
100+
void shouldRetainEnumArray() {
101+
102+
Object result = converter.writeValue(new MySimpleEnum[] { MySimpleEnum.ON }, TypeInformation.OBJECT);
103+
104+
assertThat(result).isEqualTo(new MySimpleEnum[] { MySimpleEnum.ON });
105+
}
106+
107+
@Test // GH-1593
86108
void shouldConvertStringToEnum() {
87109

88110
Object result = converter.readValue("OFF", TypeInformation.of(MyEnum.class));
@@ -93,8 +115,7 @@ void shouldConvertStringToEnum() {
93115
@Test // GH-1046
94116
void shouldConvertArrayElementsToTargetElementType() throws NoSuchMethodException {
95117

96-
TypeInformation<?> typeInformation = TypeInformation
97-
.fromReturnTypeOf(EntityWithArray.class.getMethod("getFloats"));
118+
TypeInformation<?> typeInformation = TypeInformation.fromReturnTypeOf(EntityWithArray.class.getMethod("getFloats"));
98119
Double[] value = { 1.2d, 1.3d, 1.4d };
99120
Object result = converter.readValue(value, typeInformation);
100121
assertThat(result).isEqualTo(Arrays.asList(1.2f, 1.3f, 1.4f));
@@ -157,4 +178,8 @@ static class MyEntityWithConvertibleProperty {
157178
enum MyEnum {
158179
ON, OFF;
159180
}
181+
182+
enum MySimpleEnum {
183+
ON, OFF;
184+
}
160185
}

0 commit comments

Comments
 (0)