Skip to content

Commit 74ca3d7

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 842a4f0 commit 74ca3d7

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;
@@ -165,44 +166,19 @@ public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
165166

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

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

175173
return getPotentiallyConvertedSimpleWrite(value);
176174
}
177175

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

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

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

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