Skip to content

Commit b5c90d1

Browse files
committed
#30 - Address review feedback.
Introduce ArrayColumns type to encapsulate Dialect-specific array support. Apply array conversion for properties that do not match the native array type. Add integration tests for Postgres array columns.
1 parent 299c0c5 commit b5c90d1

File tree

9 files changed

+316
-40
lines changed

9 files changed

+316
-40
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package org.springframework.data.r2dbc.dialect;
2+
3+
/**
4+
* Interface declaring methods that express how a dialect supports array-typed columns.
5+
*
6+
* @author Mark Paluch
7+
*/
8+
public interface ArrayColumns {
9+
10+
/**
11+
* Returns {@literal true} if the dialect supports array-typed columns.
12+
*
13+
* @return {@literal true} if the dialect supports array-typed columns.
14+
*/
15+
boolean isSupported();
16+
17+
/**
18+
* Translate the {@link Class user type} of an array into the dialect-specific type. This method considers only the
19+
* component type.
20+
*
21+
* @param userType component type of the array.
22+
* @return the dialect-supported array type.
23+
* @throws UnsupportedOperationException if array typed columns are not supported.
24+
* @throws IllegalArgumentException if the {@code userType} is not a supported array type.
25+
*/
26+
Class<?> getArrayType(Class<?> userType);
27+
28+
/**
29+
* Default {@link ArrayColumns} implementation for dialects that do not support array-typed columns.
30+
*/
31+
enum Unsupported implements ArrayColumns {
32+
33+
INSTANCE;
34+
35+
/*
36+
* (non-Javadoc)
37+
* @see org.springframework.data.r2dbc.dialect.ArrayColumns#isSupported()
38+
*/
39+
@Override
40+
public boolean isSupported() {
41+
return false;
42+
}
43+
44+
/*
45+
* (non-Javadoc)
46+
* @see org.springframework.data.r2dbc.dialect.ArrayColumns#getArrayType(java.lang.Class)
47+
*/
48+
@Override
49+
public Class<?> getArrayType(Class<?> userType) {
50+
throw new UnsupportedOperationException("Array types not supported");
51+
}
52+
}
53+
}

src/main/java/org/springframework/data/r2dbc/dialect/Dialect.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.HashSet;
66

77
import org.springframework.data.mapping.model.SimpleTypeHolder;
8+
import org.springframework.data.r2dbc.dialect.ArrayColumns.Unsupported;
89

910
/**
1011
* Represents a dialect that is implemented by a particular database.
@@ -61,12 +62,11 @@ default SimpleTypeHolder getSimpleTypeHolder() {
6162
LimitClause limit();
6263

6364
/**
64-
* Returns {@literal true} whether this dialect supports array-typed column. Collection-typed columns can map their
65-
* content to native array types.
65+
* Returns the array support object that describes how array-typed columns are supported by this dialect.
6666
*
67-
* @return {@literal true} whether this dialect supports array-typed columns.
67+
* @return the array support object that describes how array-typed columns are supported by this dialect.
6868
*/
69-
default boolean supportsArrayColumns() {
70-
return false;
69+
default ArrayColumns getArraySupport() {
70+
return Unsupported.INSTANCE;
7171
}
7272
}

src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package org.springframework.data.r2dbc.dialect;
22

3+
import lombok.RequiredArgsConstructor;
4+
35
import java.net.InetAddress;
46
import java.net.URI;
57
import java.net.URL;
68
import java.util.Arrays;
79
import java.util.Collection;
810
import java.util.HashSet;
9-
import java.util.List;
1011
import java.util.Set;
1112
import java.util.UUID;
1213

14+
import org.springframework.data.mapping.model.SimpleTypeHolder;
15+
import org.springframework.util.Assert;
16+
import org.springframework.util.ClassUtils;
17+
1318
/**
1419
* An SQL dialect for Postgres.
1520
*
@@ -18,7 +23,7 @@
1823
public class PostgresDialect implements Dialect {
1924

2025
private static final Set<Class<?>> SIMPLE_TYPES = new HashSet<>(
21-
Arrays.asList(List.class, Collection.class, String[].class, UUID.class, URL.class, URI.class, InetAddress.class));
26+
Arrays.asList(UUID.class, URL.class, URI.class, InetAddress.class));
2227

2328
/**
2429
* Singleton instance.
@@ -57,6 +62,8 @@ public Position getClausePosition() {
5762
}
5863
};
5964

65+
private final PostgresArrayColumns ARRAY_COLUMNS = new PostgresArrayColumns(getSimpleTypeHolder());
66+
6067
/*
6168
* (non-Javadoc)
6269
* @see org.springframework.data.r2dbc.dialect.Dialect#getBindMarkersFactory()
@@ -95,10 +102,41 @@ public LimitClause limit() {
95102

96103
/*
97104
* (non-Javadoc)
98-
* @see org.springframework.data.r2dbc.dialect.Dialect#supportsArrayColumns()
105+
* @see org.springframework.data.r2dbc.dialect.Dialect#getArraySupport()
99106
*/
100107
@Override
101-
public boolean supportsArrayColumns() {
102-
return true;
108+
public ArrayColumns getArraySupport() {
109+
return ARRAY_COLUMNS;
110+
}
111+
112+
@RequiredArgsConstructor
113+
static class PostgresArrayColumns implements ArrayColumns {
114+
115+
private final SimpleTypeHolder simpleTypes;
116+
117+
/*
118+
* (non-Javadoc)
119+
* @see org.springframework.data.r2dbc.dialect.ArrayColumns#isSupported()
120+
*/
121+
@Override
122+
public boolean isSupported() {
123+
return true;
124+
}
125+
126+
/*
127+
* (non-Javadoc)
128+
* @see org.springframework.data.r2dbc.dialect.ArrayColumns#getArrayType(java.lang.Class)
129+
*/
130+
@Override
131+
public Class<?> getArrayType(Class<?> userType) {
132+
133+
Assert.notNull(userType, "Array component type must not be null");
134+
135+
if (!simpleTypes.isSimpleType(userType)) {
136+
throw new IllegalArgumentException("Unsupported array type: " + ClassUtils.getQualifiedName(userType));
137+
}
138+
139+
return ClassUtils.resolvePrimitiveIfNecessary(userType);
140+
}
103141
}
104142
}

src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.data.domain.Sort.Order;
3939
import org.springframework.data.mapping.PersistentPropertyAccessor;
4040
import org.springframework.data.mapping.context.MappingContext;
41+
import org.springframework.data.r2dbc.dialect.ArrayColumns;
4142
import org.springframework.data.r2dbc.dialect.BindMarker;
4243
import org.springframework.data.r2dbc.dialect.BindMarkers;
4344
import org.springframework.data.r2dbc.dialect.Dialect;
@@ -249,7 +250,7 @@ private RelationalPersistentEntity<?> getPersistentEntity(Class<?> typeToRead) {
249250
private Object getWriteValue(PersistentPropertyAccessor propertyAccessor, RelationalPersistentProperty property) {
250251

251252
TypeInformation<?> type = property.getTypeInformation();
252-
Object value = relationalConverter.writeValue(propertyAccessor.getProperty(property), type);
253+
Object value = propertyAccessor.getProperty(property);
253254

254255
if (type.isCollectionLike()) {
255256

@@ -260,17 +261,28 @@ private Object getWriteValue(PersistentPropertyAccessor propertyAccessor, Relati
260261
throw new InvalidDataAccessApiUsageException("Nested entities are not supported");
261262
}
262263

263-
if (!dialect.supportsArrayColumns()) {
264+
ArrayColumns arrayColumns = dialect.getArraySupport();
265+
266+
if (!arrayColumns.isSupported()) {
264267

265268
throw new InvalidDataAccessResourceUsageException(
266269
"Dialect " + dialect.getClass().getName() + " does not support array columns");
267270
}
268271

269-
if (!property.isArray()) {
272+
return getArrayValue(arrayColumns, property, value);
273+
}
270274

271-
Object zeroLengthArray = Array.newInstance(property.getActualType(), 0);
272-
return relationalConverter.getConversionService().convert(value, zeroLengthArray.getClass());
273-
}
275+
return value;
276+
}
277+
278+
private Object getArrayValue(ArrayColumns arrayColumns, RelationalPersistentProperty property, Object value) {
279+
280+
Class<?> targetType = arrayColumns.getArrayType(property.getActualType());
281+
282+
if (!property.isArray() || !property.getActualType().equals(targetType)) {
283+
284+
Object zeroLengthArray = Array.newInstance(targetType, 0);
285+
return relationalConverter.getConversionService().convert(value, zeroLengthArray.getClass());
274286
}
275287

276288
return value;

src/main/java/org/springframework/data/r2dbc/function/convert/EntityRowMapper.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ private Object readFrom(Row row, RelationalPersistentProperty property, String p
9696
return readEntityFrom(row, property);
9797
}
9898

99-
return converter.readValue(row.get(prefix + property.getColumnName()), property.getTypeInformation());
99+
Object value = row.get(prefix + property.getColumnName());
100+
return converter.readValue(value, property.getTypeInformation());
100101

101102
} catch (Exception o_O) {
102103
throw new MappingException(String.format("Could not read property %s from result set!", property), o_O);
@@ -156,9 +157,7 @@ public <T> T getParameterValue(Parameter<T, RelationalPersistentProperty> parame
156157
String column = prefix + property.getColumnName();
157158

158159
try {
159-
160-
Object value = converter.readValue(resultSet.get(column), property.getTypeInformation());
161-
return converter.getConversionService().convert(value, parameter.getType().getType());
160+
return converter.getConversionService().convert(resultSet.get(column), parameter.getType().getType());
162161
} catch (Exception o_O) {
163162
throw new MappingException(String.format("Couldn't read column %s from Row.", column), o_O);
164163
}

src/test/java/org/springframework/data/r2dbc/dialect/PostgresDialectUnitTests.java

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package org.springframework.data.r2dbc.dialect;
22

33
import static org.assertj.core.api.Assertions.*;
4+
import static org.assertj.core.api.SoftAssertions.*;
45

5-
import java.util.Collection;
66
import java.util.List;
77

88
import org.junit.Test;
@@ -28,35 +28,50 @@ public void shouldUsePostgresPlaceholders() {
2828
}
2929

3030
@Test // gh-30
31-
public void shouldConsiderCollectionTypesAsSimple() {
31+
public void shouldConsiderSimpleTypes() {
3232

3333
SimpleTypeHolder holder = PostgresDialect.INSTANCE.getSimpleTypeHolder();
3434

35-
assertThat(holder.isSimpleType(List.class)).isTrue();
36-
assertThat(holder.isSimpleType(Collection.class)).isTrue();
35+
assertSoftly(it -> {
36+
it.assertThat(holder.isSimpleType(String.class)).isTrue();
37+
it.assertThat(holder.isSimpleType(int.class)).isTrue();
38+
it.assertThat(holder.isSimpleType(Integer.class)).isTrue();
39+
});
3740
}
3841

3942
@Test // gh-30
40-
public void shouldConsiderStringArrayTypeAsSimple() {
43+
public void shouldSupportArrays() {
4144

42-
SimpleTypeHolder holder = PostgresDialect.INSTANCE.getSimpleTypeHolder();
45+
ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
46+
47+
assertThat(arrayColumns.isSupported()).isTrue();
48+
}
49+
50+
@Test // gh-30
51+
public void shouldUseBoxedArrayTypesForPrimitiveTypes() {
4352

44-
assertThat(holder.isSimpleType(String[].class)).isTrue();
53+
ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
4554

46-
@Test // gh-30
47-
public void shouldConsiderIntArrayTypeAsSimple() {
55+
assertSoftly(it -> {
56+
it.assertThat(arrayColumns.getArrayType(int.class)).isEqualTo(Integer.class);
57+
it.assertThat(arrayColumns.getArrayType(double.class)).isEqualTo(Double.class);
58+
it.assertThat(arrayColumns.getArrayType(String.class)).isEqualTo(String.class);
59+
});
60+
}
4861

49-
SimpleTypeHolder holder = PostgresDialect.INSTANCE.getSimpleTypeHolder();
62+
@Test // gh-30
63+
public void shouldRejectNonSimpleArrayTypes() {
5064

51-
assertThat(holder.isSimpleType(int[].class)).isTrue();
52-
}
65+
ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
5366

54-
@Test // gh-30
55-
public void shouldConsiderIntegerArrayTypeAsSimple() {
67+
assertThatThrownBy(() -> arrayColumns.getArrayType(getClass())).isInstanceOf(IllegalArgumentException.class);
68+
}
69+
70+
@Test // gh-30
71+
public void shouldRejectNestedCollections() {
5672

57-
SimpleTypeHolder holder = PostgresDialect.INSTANCE.getSimpleTypeHolder();
73+
ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
5874

59-
assertThat(holder.isSimpleType(Integer[].class)).isTrue();
60-
}
75+
assertThatThrownBy(() -> arrayColumns.getArrayType(List.class)).isInstanceOf(IllegalArgumentException.class);
6176
}
6277
}

src/test/java/org/springframework/data/r2dbc/dialect/SqlServerDialectUnitTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,13 @@ public void shouldConsiderUuidAsSimple() {
3333

3434
assertThat(holder.isSimpleType(UUID.class)).isTrue();
3535
}
36+
37+
@Test // gh-30
38+
public void shouldNotSupportArrays() {
39+
40+
ArrayColumns arrayColumns = SqlServerDialect.INSTANCE.getArraySupport();
41+
42+
assertThat(arrayColumns.isSupported()).isFalse();
43+
assertThatThrownBy(() -> arrayColumns.getArrayType(String.class)).isInstanceOf(UnsupportedOperationException.class);
44+
}
3645
}

0 commit comments

Comments
 (0)