Skip to content

Commit 55c6c0e

Browse files
schaudermp911de
authored andcommitted
Properly convert primitive array arguments.
Closes #945 Original pull request: #949.
1 parent f20192c commit 55c6c0e

File tree

5 files changed

+148
-10
lines changed

5 files changed

+148
-10
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,97 @@ static byte[] toPrimitiveByteArray(Byte[] byteArray) {
4242
}
4343
return bytes;
4444
}
45+
46+
static Byte[] toObjectArray(byte[] primitiveArray) {
47+
48+
Byte[] objects = new Byte[primitiveArray.length];
49+
for (int i = 0; i < primitiveArray.length; i++) {
50+
objects[i] = primitiveArray[i];
51+
}
52+
return objects;
53+
}
54+
55+
static Short[] toObjectArray(short[] primitiveArray) {
56+
57+
Short[] objects = new Short[primitiveArray.length];
58+
for (int i = 0; i < primitiveArray.length; i++) {
59+
objects[i] = primitiveArray[i];
60+
}
61+
return objects;
62+
}
63+
64+
static Character[] toObjectArray(char[] primitiveArray) {
65+
66+
Character[] objects = new Character[primitiveArray.length];
67+
for (int i = 0; i < primitiveArray.length; i++) {
68+
objects[i] = primitiveArray[i];
69+
}
70+
return objects;
71+
}
72+
73+
static Integer[] toObjectArray(int[] primitiveArray) {
74+
75+
Integer[] objects = new Integer[primitiveArray.length];
76+
for (int i = 0; i < primitiveArray.length; i++) {
77+
objects[i] = primitiveArray[i];
78+
}
79+
return objects;
80+
}
81+
82+
static Long[] toObjectArray(long[] primitiveArray) {
83+
84+
Long[] objects = new Long[primitiveArray.length];
85+
for (int i = 0; i < primitiveArray.length; i++) {
86+
objects[i] = primitiveArray[i];
87+
}
88+
return objects;
89+
}
90+
91+
static Float[] toObjectArray(float[] primitiveArray) {
92+
93+
Float[] objects = new Float[primitiveArray.length];
94+
for (int i = 0; i < primitiveArray.length; i++) {
95+
objects[i] = primitiveArray[i];
96+
}
97+
return objects;
98+
}
99+
100+
static Double[] toObjectArray(double[] primitiveArray) {
101+
102+
Double[] objects = new Double[primitiveArray.length];
103+
for (int i = 0; i < primitiveArray.length; i++) {
104+
objects[i] = primitiveArray[i];
105+
}
106+
return objects;
107+
}
108+
109+
static Object[] convertToObjectArray(Object unknownArray) {
110+
111+
Class<?> componentType = unknownArray.getClass().getComponentType();
112+
113+
if (componentType.isPrimitive()) {
114+
if (componentType == byte.class) {
115+
return toObjectArray((byte[]) unknownArray);
116+
}
117+
if (componentType == short.class) {
118+
return toObjectArray((short[]) unknownArray);
119+
}
120+
if (componentType == char.class) {
121+
return toObjectArray((char[]) unknownArray);
122+
}
123+
if (componentType == int.class) {
124+
return toObjectArray((int[]) unknownArray);
125+
}
126+
if (componentType == long.class) {
127+
return toObjectArray((long[]) unknownArray);
128+
}
129+
if (componentType == float.class) {
130+
return toObjectArray((float[]) unknownArray);
131+
}
132+
if (componentType == double.class) {
133+
return toObjectArray((double[]) unknownArray);
134+
}
135+
}
136+
return (Object[]) unknownArray;
137+
}
45138
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import org.slf4j.Logger;
2626
import org.slf4j.LoggerFactory;
27-
2827
import org.springframework.context.ApplicationContext;
2928
import org.springframework.context.ApplicationContextAware;
3029
import org.springframework.core.convert.ConverterNotFoundException;
@@ -317,7 +316,9 @@ public JdbcValue writeJdbcValue(@Nullable Object value, Class<?> columnType, int
317316

318317
Class<?> componentType = convertedValue.getClass().getComponentType();
319318
if (componentType != byte.class && componentType != Byte.class) {
320-
return JdbcValue.of(typeFactory.createArray((Object[]) convertedValue), JDBCType.ARRAY);
319+
320+
Object[] objectArray = ArrayUtil.convertToObjectArray(convertedValue);
321+
return JdbcValue.of(typeFactory.createArray(objectArray), JDBCType.ARRAY);
321322
}
322323

323324
if (componentType == Byte.class) {
@@ -333,7 +334,7 @@ private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) {
333334
if (canWriteAsJdbcValue(value)) {
334335

335336
Object converted = writeValue(value, ClassTypeInformation.from(JdbcValue.class));
336-
if(converted instanceof JdbcValue) {
337+
if (converted instanceof JdbcValue) {
337338
return (JdbcValue) converted;
338339
}
339340

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverterUnitTests.java

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
package org.springframework.data.jdbc.core.convert;
1717

1818
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.Mockito.*;
1920

2021
import lombok.Data;
2122

23+
import java.sql.Array;
2224
import java.sql.Timestamp;
2325
import java.time.Instant;
2426
import java.time.LocalDate;
@@ -35,8 +37,10 @@
3537
import org.springframework.data.annotation.Id;
3638
import org.springframework.data.jdbc.core.mapping.AggregateReference;
3739
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
40+
import org.springframework.data.jdbc.support.JdbcUtil;
3841
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3942
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
43+
import org.springframework.data.relational.core.sql.IdentifierProcessing;
4044
import org.springframework.data.util.ClassTypeInformation;
4145

4246
/**
@@ -47,9 +51,15 @@
4751
public class BasicJdbcConverterUnitTests {
4852

4953
JdbcMappingContext context = new JdbcMappingContext();
50-
BasicJdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
51-
throw new UnsupportedOperationException();
52-
});
54+
StubbedJdbcTypeFactory typeFactory = new StubbedJdbcTypeFactory();
55+
BasicJdbcConverter converter = new BasicJdbcConverter( //
56+
context, //
57+
(identifier, path) -> {
58+
throw new UnsupportedOperationException();
59+
}, //
60+
new JdbcCustomConversions(), //
61+
typeFactory, IdentifierProcessing.ANSI //
62+
);
5363

5464
@Test // DATAJDBC-104, DATAJDBC-1384
5565
public void testTargetTypesForPropertyType() {
@@ -110,14 +120,25 @@ void conversionOfDateLikeValueAndBackYieldsOriginalValue() {
110120
LocalDateTime testLocalDateTime = LocalDateTime.of(2001, 2, 3, 4, 5, 6, 123456789);
111121
checkConversionToTimestampAndBack(softly, persistentEntity, "localDateTime", testLocalDateTime);
112122
checkConversionToTimestampAndBack(softly, persistentEntity, "localDate", LocalDate.of(2001, 2, 3));
113-
checkConversionToTimestampAndBack(softly, persistentEntity, "localTime", LocalTime.of(1, 2, 3,123456789));
114-
checkConversionToTimestampAndBack(softly, persistentEntity, "instant", testLocalDateTime.toInstant(ZoneOffset.UTC));
123+
checkConversionToTimestampAndBack(softly, persistentEntity, "localTime", LocalTime.of(1, 2, 3, 123456789));
124+
checkConversionToTimestampAndBack(softly, persistentEntity, "instant",
125+
testLocalDateTime.toInstant(ZoneOffset.UTC));
115126
});
116127

117128
}
118129

119-
private void checkConversionToTimestampAndBack(SoftAssertions softly, RelationalPersistentEntity<?> persistentEntity, String propertyName,
120-
Object value) {
130+
@Test // #945
131+
void conversionOfPrimitiveArrays() {
132+
133+
int[] ints = { 1, 2, 3, 4, 5 };
134+
JdbcValue converted = converter.writeJdbcValue(ints, ints.getClass(), JdbcUtil.sqlTypeFor(ints.getClass()));
135+
136+
assertThat(converted.getValue()).isInstanceOf(Array.class);
137+
assertThat(typeFactory.arraySource).containsExactly(1, 2, 3, 4, 5);
138+
}
139+
140+
private void checkConversionToTimestampAndBack(SoftAssertions softly, RelationalPersistentEntity<?> persistentEntity,
141+
String propertyName, Object value) {
121142

122143
RelationalPersistentProperty property = persistentEntity.getRequiredPersistentProperty(propertyName);
123144

@@ -165,4 +186,14 @@ private enum SomeEnum {
165186

166187
@SuppressWarnings("unused")
167188
private static class OtherEntity {}
189+
190+
private static class StubbedJdbcTypeFactory implements JdbcTypeFactory {
191+
public Object[] arraySource;
192+
193+
@Override
194+
public Array createArray(Object[] value) {
195+
arraySource = value;
196+
return mock(Array.class);
197+
}
198+
}
168199
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.ArrayList;
2929
import java.util.List;
3030

31+
import lombok.ToString;
3132
import org.junit.jupiter.api.BeforeEach;
3233
import org.junit.jupiter.api.Test;
3334
import org.junit.jupiter.api.extension.ExtendWith;
@@ -42,7 +43,9 @@
4243
import org.springframework.data.jdbc.repository.query.Query;
4344
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
4445
import org.springframework.data.jdbc.testing.AssumeFeatureTestExecutionListener;
46+
import org.springframework.data.jdbc.testing.EnabledOnFeature;
4547
import org.springframework.data.jdbc.testing.TestConfiguration;
48+
import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
4649
import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent;
4750
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
4851
import org.springframework.data.repository.CrudRepository;
@@ -395,6 +398,12 @@ public void countByQueryDerivation() {
395398
assertThat(repository.countByName(one.getName())).isEqualTo(2);
396399
}
397400

401+
@Test // #945
402+
@EnabledOnFeature(TestDatabaseFeatures.Feature.IS_POSTGRES)
403+
public void usePrimitiveArrayAsArgument() {
404+
assertThat(repository.unnestPrimitive(new int[]{1, 2, 3})).containsExactly(1,2,3);
405+
}
406+
398407
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
399408

400409
List<DummyEntity> findAllByNamedQuery();
@@ -420,6 +429,9 @@ interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
420429
boolean existsByName(String name);
421430

422431
int countByName(String name);
432+
433+
@Query("select unnest( :ids )")
434+
List<Integer> unnestPrimitive(@Param("ids") int[] ids);
423435
}
424436

425437
@Configuration

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestDatabaseFeatures.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ public enum Feature {
115115
SUPPORTS_ARRAYS(TestDatabaseFeatures::supportsArrays), //
116116
SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES(TestDatabaseFeatures::supportsGeneratedIdsInReferencedEntities), //
117117
SUPPORTS_NANOSECOND_PRECISION(TestDatabaseFeatures::supportsNanosecondPrecision), //
118+
IS_POSTGRES(f -> f.databaseIs(Database.PostgreSql)), //
118119
IS_HSQL(f -> f.databaseIs(Database.Hsql));
119120

120121
private final Consumer<TestDatabaseFeatures> featureMethod;

0 commit comments

Comments
 (0)