Skip to content

Commit d41957d

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 d41957d

File tree

9 files changed

+306
-41
lines changed

9 files changed

+306
-41
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 & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
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;
@@ -10,6 +12,10 @@
1012
import java.util.Set;
1113
import java.util.UUID;
1214

15+
import org.springframework.data.mapping.model.SimpleTypeHolder;
16+
import org.springframework.util.Assert;
17+
import org.springframework.util.ClassUtils;
18+
1319
/**
1420
* An SQL dialect for Postgres.
1521
*
@@ -18,7 +24,7 @@
1824
public class PostgresDialect implements Dialect {
1925

2026
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));
27+
Arrays.asList(List.class, Collection.class, UUID.class, URL.class, URI.class, InetAddress.class));
2228

2329
/**
2430
* Singleton instance.
@@ -57,6 +63,8 @@ public Position getClausePosition() {
5763
}
5864
};
5965

66+
private final PostgresArrayColumns ARRAY_COLUMNS = new PostgresArrayColumns(getSimpleTypeHolder());
67+
6068
/*
6169
* (non-Javadoc)
6270
* @see org.springframework.data.r2dbc.dialect.Dialect#getBindMarkersFactory()
@@ -95,10 +103,41 @@ public LimitClause limit() {
95103

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

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: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
import static org.assertj.core.api.Assertions.*;
44

5-
import java.util.Collection;
6-
import java.util.List;
7-
5+
import org.assertj.core.api.SoftAssertions;
86
import org.junit.Test;
97
import org.springframework.data.mapping.model.SimpleTypeHolder;
108

@@ -28,35 +26,40 @@ public void shouldUsePostgresPlaceholders() {
2826
}
2927

3028
@Test // gh-30
31-
public void shouldConsiderCollectionTypesAsSimple() {
29+
public void shouldConsiderSimpleTypes() {
3230

3331
SimpleTypeHolder holder = PostgresDialect.INSTANCE.getSimpleTypeHolder();
3432

35-
assertThat(holder.isSimpleType(List.class)).isTrue();
36-
assertThat(holder.isSimpleType(Collection.class)).isTrue();
33+
SoftAssertions.assertSoftly(it -> {
34+
it.assertThat(holder.isSimpleType(String.class)).isTrue();
35+
it.assertThat(holder.isSimpleType(int.class)).isTrue();
36+
it.assertThat(holder.isSimpleType(Integer.class)).isTrue();
37+
});
3738
}
3839

3940
@Test // gh-30
40-
public void shouldConsiderStringArrayTypeAsSimple() {
41+
public void shouldSupportArrays() {
4142

42-
SimpleTypeHolder holder = PostgresDialect.INSTANCE.getSimpleTypeHolder();
43+
ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
4344

44-
assertThat(holder.isSimpleType(String[].class)).isTrue();
45+
assertThat(arrayColumns.isSupported()).isTrue();
46+
}
4547

46-
@Test // gh-30
47-
public void shouldConsiderIntArrayTypeAsSimple() {
48+
@Test // gh-30
49+
public void shouldUseBoxedArrayTypesForPrimitiveTypes() {
4850

49-
SimpleTypeHolder holder = PostgresDialect.INSTANCE.getSimpleTypeHolder();
51+
ArrayColumns arrayColumns = PostgresDialect.INSTANCE.getArraySupport();
5052

51-
assertThat(holder.isSimpleType(int[].class)).isTrue();
52-
}
53+
assertThat(arrayColumns.getArrayType(int.class)).isEqualTo(Integer.class);
54+
assertThat(arrayColumns.getArrayType(double.class)).isEqualTo(Double.class);
55+
assertThat(arrayColumns.getArrayType(String.class)).isEqualTo(String.class);
56+
}
5357

54-
@Test // gh-30
55-
public void shouldConsiderIntegerArrayTypeAsSimple() {
58+
@Test // gh-30
59+
public void shouldRejectNonSimpleArrayTypes() {
5660

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

59-
assertThat(holder.isSimpleType(Integer[].class)).isTrue();
60-
}
63+
assertThatThrownBy(() -> arrayColumns.getArrayType(getClass())).isInstanceOf(IllegalArgumentException.class);
6164
}
6265
}

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)