Skip to content

Commit f71b41f

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 a6c855f commit f71b41f

File tree

3 files changed

+117
-44
lines changed

3 files changed

+117
-44
lines changed

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

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

18+
import static org.mockito.Mockito.*;
19+
import static org.springframework.data.r2dbc.testing.Assertions.*;
20+
1821
import io.r2dbc.postgresql.codec.Interval;
19-
import org.junit.jupiter.api.Test;
20-
import org.springframework.core.convert.converter.Converter;
21-
import org.springframework.data.convert.ReadingConverter;
22-
import org.springframework.data.convert.WritingConverter;
23-
import org.springframework.data.r2dbc.convert.EnumWriteSupport;
24-
import org.springframework.data.r2dbc.dialect.PostgresDialect;
25-
import org.springframework.data.r2dbc.mapping.OutboundRow;
26-
import org.springframework.data.relational.core.sql.SqlIdentifier;
2722

2823
import java.time.Duration;
2924
import java.util.ArrayList;
@@ -33,7 +28,18 @@
3328
import java.util.List;
3429
import java.util.Set;
3530

36-
import static org.springframework.data.r2dbc.testing.Assertions.*;
31+
import org.junit.jupiter.api.Test;
32+
import org.springframework.core.convert.converter.Converter;
33+
import org.springframework.data.convert.ReadingConverter;
34+
import org.springframework.data.convert.WritingConverter;
35+
import org.springframework.data.r2dbc.convert.EnumWriteSupport;
36+
import org.springframework.data.r2dbc.core.StatementMapper.InsertSpec;
37+
import org.springframework.data.r2dbc.dialect.PostgresDialect;
38+
import org.springframework.data.r2dbc.mapping.OutboundRow;
39+
import org.springframework.data.relational.core.sql.SqlIdentifier;
40+
import org.springframework.r2dbc.core.Parameter;
41+
import org.springframework.r2dbc.core.PreparedOperation;
42+
import org.springframework.r2dbc.core.binding.BindTarget;
3743

3844
/**
3945
* {@link PostgresDialect} specific tests for {@link ReactiveDataAccessStrategy}.
@@ -58,6 +64,20 @@ void shouldConvertPrimitiveMultidimensionArrayToWrapper() {
5864
assertThat(row).withColumn("myarray").hasValueInstanceOf(Integer[][].class);
5965
}
6066

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

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

162163
if (getConversions().isSimpleType(value.getClass())) {
163164

164-
if (TypeInformation.OBJECT != type) {
165-
166-
if (conversionService.canConvert(value.getClass(), type.getType())) {
167-
value = conversionService.convert(value, type.getType());
168-
}
165+
if (TypeInformation.OBJECT != type && conversionService.canConvert(value.getClass(), type.getType())) {
166+
value = conversionService.convert(value, type.getType());
169167
}
170168

171169
return getPotentiallyConvertedSimpleWrite(value);
172170
}
173171

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

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

204180
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(value.getClass());
@@ -212,6 +188,57 @@ public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
212188
return conversionService.convert(value, type.getType());
213189
}
214190

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

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

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717

1818
import static org.assertj.core.api.Assertions.*;
1919

20+
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.List;
22-
import java.util.Set;
23+
import java.util.function.Function;
2324

2425
import org.junit.jupiter.api.BeforeEach;
2526
import org.junit.jupiter.api.Test;
26-
import org.springframework.core.convert.converter.GenericConverter;
2727
import org.springframework.data.convert.ConverterBuilder;
28+
import org.springframework.data.convert.ConverterBuilder.ConverterAware;
2829
import org.springframework.data.convert.CustomConversions;
2930
import org.springframework.data.mapping.PersistentPropertyAccessor;
3031
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -46,8 +47,13 @@ class BasicRelationalConverterUnitTests {
4647
@BeforeEach
4748
public void before() throws Exception {
4849

49-
Set<GenericConverter> converters = ConverterBuilder.writing(MyValue.class, String.class, MyValue::foo)
50-
.andReading(MyValue::new).getConverters();
50+
List<Object> converters = new ArrayList<>();
51+
converters.addAll(
52+
ConverterBuilder.writing(MyValue.class, String.class, MyValue::foo).andReading(MyValue::new).getConverters());
53+
54+
ConverterAware converterAware = ConverterBuilder
55+
.writing(MySimpleEnum.class, MySimpleEnum.class, Function.identity()).andReading(mySimpleEnum -> mySimpleEnum);
56+
converters.addAll(converterAware.getConverters());
5157

5258
CustomConversions conversions = new CustomConversions(CustomConversions.StoreConversions.NONE, converters);
5359
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
@@ -79,7 +85,23 @@ void shouldConvertEnumToString() {
7985
assertThat(result).isEqualTo("ON");
8086
}
8187

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

85107
Object result = converter.readValue("OFF", TypeInformation.of(MyEnum.class));
@@ -145,4 +167,8 @@ static class MyEntityWithConvertibleProperty {
145167
enum MyEnum {
146168
ON, OFF;
147169
}
170+
171+
enum MySimpleEnum {
172+
ON, OFF;
173+
}
148174
}

0 commit comments

Comments
 (0)