diff --git a/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java b/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java index e6b11e8..4ebfcb5 100644 --- a/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java +++ b/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java @@ -17,15 +17,20 @@ package org.springframework.data.r2dbc.config; import io.r2dbc.spi.ConnectionFactory; +import java.util.Collections; import java.util.Optional; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.CustomConversions; +import org.springframework.data.convert.CustomConversions.StoreConversions; import org.springframework.data.r2dbc.dialect.Database; import org.springframework.data.r2dbc.dialect.Dialect; import org.springframework.data.r2dbc.function.DatabaseClient; import org.springframework.data.r2dbc.function.DefaultReactiveDataAccessStrategy; import org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy; +import org.springframework.data.r2dbc.function.convert.R2dbcCustomConversions; import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator; import org.springframework.data.r2dbc.support.SqlErrorCodeR2dbcExceptionTranslator; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; @@ -95,15 +100,21 @@ public abstract class AbstractR2dbcConfiguration { * Register a {@link RelationalMappingContext} and apply an optional {@link NamingStrategy}. * * @param namingStrategy optional {@link NamingStrategy}. Use {@link NamingStrategy#INSTANCE} as fallback. + * @param r2dbcCustomConversions customized R2DBC conversions. * @return must not be {@literal null}. * @throws IllegalArgumentException if any of the required args is {@literal null}. */ @Bean - public RelationalMappingContext r2dbcMappingContext(Optional namingStrategy) { + public RelationalMappingContext r2dbcMappingContext(Optional namingStrategy, + R2dbcCustomConversions r2dbcCustomConversions) { Assert.notNull(namingStrategy, "NamingStrategy must not be null!"); - return new RelationalMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE)); + RelationalMappingContext relationalMappingContext = new RelationalMappingContext( + namingStrategy.orElse(NamingStrategy.INSTANCE)); + relationalMappingContext.setSimpleTypeHolder(r2dbcCustomConversions.getSimpleTypeHolder()); + + return relationalMappingContext; } /** @@ -111,17 +122,37 @@ public abstract class AbstractR2dbcConfiguration { * RelationalMappingContext}. * * @param mappingContext the configured {@link RelationalMappingContext}. + * @param r2dbcCustomConversions customized R2DBC conversions. * @return must not be {@literal null}. - * @see #r2dbcMappingContext(Optional) + * @see #r2dbcMappingContext(Optional, R2dbcCustomConversions) * @see #getDialect(ConnectionFactory) * @throws IllegalArgumentException if any of the {@literal mappingContext} is {@literal null}. */ @Bean - public ReactiveDataAccessStrategy reactiveDataAccessStrategy(RelationalMappingContext mappingContext) { + public ReactiveDataAccessStrategy reactiveDataAccessStrategy(RelationalMappingContext mappingContext, + R2dbcCustomConversions r2dbcCustomConversions) { Assert.notNull(mappingContext, "MappingContext must not be null!"); - return new DefaultReactiveDataAccessStrategy(getDialect(connectionFactory()), - new BasicRelationalConverter(mappingContext)); + + BasicRelationalConverter converter = new BasicRelationalConverter(mappingContext, r2dbcCustomConversions); + + return new DefaultReactiveDataAccessStrategy(getDialect(connectionFactory()), converter); + } + + /** + * Register custom {@link Converter}s in a {@link CustomConversions} object if required. These + * {@link CustomConversions} will be registered with the {@link BasicRelationalConverter} and + * {@link #r2dbcMappingContext(Optional, R2dbcCustomConversions)}. Returns an empty {@link R2dbcCustomConversions} + * instance by default. + * + * @return must not be {@literal null}. + */ + @Bean + public R2dbcCustomConversions r2dbcCustomConversions() { + + Dialect dialect = getDialect(connectionFactory()); + StoreConversions storeConversions = StoreConversions.of(dialect.getSimpleTypeHolder()); + return new R2dbcCustomConversions(storeConversions, Collections.emptyList()); } /** diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/Dialect.java b/src/main/java/org/springframework/data/r2dbc/dialect/Dialect.java index 3371d62..411f383 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/Dialect.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/Dialect.java @@ -1,5 +1,11 @@ package org.springframework.data.r2dbc.dialect; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + +import org.springframework.data.mapping.model.SimpleTypeHolder; + /** * Represents a dialect that is implemented by a particular database. * @@ -26,10 +32,41 @@ public interface Dialect { @Deprecated String generatedKeysClause(); + /** + * Return a collection of types that are natively supported by this database/driver. Defaults to + * {@link Collections#emptySet()}. + * + * @return a collection of types that are natively supported by this database/driver. Defaults to + * {@link Collections#emptySet()}. + */ + default Collection> getSimpleTypes() { + return Collections.emptySet(); + } + + /** + * Return the {@link SimpleTypeHolder} for this dialect. + * + * @return the {@link SimpleTypeHolder} for this dialect. + * @see #getSimpleTypes() + */ + default SimpleTypeHolder getSimpleTypeHolder() { + return new SimpleTypeHolder(new HashSet<>(getSimpleTypes()), true); + } + /** * Return the {@link LimitClause} used by this dialect. * * @return the {@link LimitClause} used by this dialect. */ LimitClause limit(); + + /** + * Returns {@literal true} whether this dialect supports array-typed column. Collection-typed columns can map their + * content to native array types. + * + * @return {@literal true} whether this dialect supports array-typed columns. + */ + default boolean supportsArrayColumns() { + return false; + } } diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java b/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java index fd5da05..8d0c0b3 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java @@ -1,5 +1,15 @@ package org.springframework.data.r2dbc.dialect; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + /** * An SQL dialect for Postgres. * @@ -7,6 +17,9 @@ package org.springframework.data.r2dbc.dialect; */ public class PostgresDialect implements Dialect { + private static final Set> SIMPLE_TYPES = new HashSet<>( + Arrays.asList(List.class, Collection.class, String[].class, UUID.class, URL.class, URI.class, InetAddress.class)); + /** * Singleton instance. */ @@ -62,6 +75,15 @@ public class PostgresDialect implements Dialect { return "RETURNING *"; } + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.dialect.Dialect#getSimpleTypesKeys() + */ + @Override + public Collection> getSimpleTypes() { + return SIMPLE_TYPES; + } + /* * (non-Javadoc) * @see org.springframework.data.r2dbc.dialect.Dialect#limit() @@ -70,4 +92,13 @@ public class PostgresDialect implements Dialect { public LimitClause limit() { return LIMIT_CLAUSE; } + + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.dialect.Dialect#supportsArrayColumns() + */ + @Override + public boolean supportsArrayColumns() { + return true; + } } diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/SqlServerDialect.java b/src/main/java/org/springframework/data/r2dbc/dialect/SqlServerDialect.java index ccbc993..bf7199e 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/SqlServerDialect.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/SqlServerDialect.java @@ -1,5 +1,11 @@ package org.springframework.data.r2dbc.dialect; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** * An SQL dialect for Microsoft SQL Server. * @@ -7,6 +13,8 @@ package org.springframework.data.r2dbc.dialect; */ public class SqlServerDialect implements Dialect { + private static final Set> SIMPLE_TYPES = new HashSet<>(Collections.singletonList(UUID.class)); + /** * Singleton instance. */ @@ -63,6 +71,15 @@ public class SqlServerDialect implements Dialect { return "select SCOPE_IDENTITY() AS GENERATED_KEYS"; } + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.dialect.Dialect#getSimpleTypesKeys() + */ + @Override + public Collection> getSimpleTypes() { + return SIMPLE_TYPES; + } + /* * (non-Javadoc) * @see org.springframework.data.r2dbc.dialect.Dialect#limit() diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java index dcf7753..5d865e7 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java @@ -19,6 +19,7 @@ import io.r2dbc.spi.Row; import io.r2dbc.spi.RowMetadata; import io.r2dbc.spi.Statement; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -29,22 +30,28 @@ import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.InvalidDataAccessResourceUsageException; +import org.springframework.data.convert.CustomConversions.StoreConversions; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.r2dbc.dialect.BindMarker; import org.springframework.data.r2dbc.dialect.BindMarkers; import org.springframework.data.r2dbc.dialect.Dialect; import org.springframework.data.r2dbc.dialect.LimitClause; import org.springframework.data.r2dbc.dialect.LimitClause.Position; import org.springframework.data.r2dbc.function.convert.EntityRowMapper; +import org.springframework.data.r2dbc.function.convert.R2dbcCustomConversions; import org.springframework.data.r2dbc.function.convert.SettableValue; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -57,8 +64,9 @@ import org.springframework.util.StringUtils; */ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStrategy { - private final RelationalConverter relationalConverter; private final Dialect dialect; + private final RelationalConverter relationalConverter; + private final MappingContext, ? extends RelationalPersistentProperty> mappingContext; /** * Creates a new {@link DefaultReactiveDataAccessStrategy} given {@link Dialect}. @@ -66,7 +74,28 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra * @param dialect the {@link Dialect} to use. */ public DefaultReactiveDataAccessStrategy(Dialect dialect) { - this(dialect, new BasicRelationalConverter(new RelationalMappingContext())); + this(dialect, createConverter(dialect)); + } + + private static BasicRelationalConverter createConverter(Dialect dialect) { + + Assert.notNull(dialect, "Dialect must not be null"); + + R2dbcCustomConversions customConversions = new R2dbcCustomConversions( + StoreConversions.of(dialect.getSimpleTypeHolder()), Collections.emptyList()); + + RelationalMappingContext context = new RelationalMappingContext(); + context.setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); + + return new BasicRelationalConverter(context, customConversions); + } + + public RelationalConverter getRelationalConverter() { + return relationalConverter; + } + + public MappingContext, ? extends RelationalPersistentProperty> getMappingContext() { + return mappingContext; } /** @@ -75,12 +104,15 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra * @param dialect the {@link Dialect} to use. * @param converter must not be {@literal null}. */ + @SuppressWarnings("unchecked") public DefaultReactiveDataAccessStrategy(Dialect dialect, RelationalConverter converter) { Assert.notNull(dialect, "Dialect must not be null"); Assert.notNull(converter, "RelationalConverter must not be null"); this.relationalConverter = converter; + this.mappingContext = (MappingContext, ? extends RelationalPersistentProperty>) relationalConverter + .getMappingContext(); this.dialect = dialect; } @@ -121,7 +153,7 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra for (RelationalPersistentProperty property : entity) { - Object value = propertyAccessor.getProperty(property); + Object value = getWriteValue(propertyAccessor, property); if (value == null) { continue; @@ -133,6 +165,31 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra return values; } + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getColumnsToUpdate(java.lang.Object) + */ + public Map getColumnsToUpdate(Object object) { + + Assert.notNull(object, "Entity object must not be null!"); + + Class userClass = ClassUtils.getUserClass(object); + RelationalPersistentEntity entity = getRequiredPersistentEntity(userClass); + + Map update = new LinkedHashMap<>(); + + PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(object); + + for (RelationalPersistentProperty property : entity) { + + Object writeValue = getWriteValue(propertyAccessor, property); + + update.put(property.getColumnName(), new SettableValue(property.getColumnName(), writeValue, property.getType())); + } + + return update; + } + /* * (non-Javadoc) * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getMappedSort(java.lang.Class, org.springframework.data.domain.Sort) @@ -181,12 +238,40 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra } private RelationalPersistentEntity getRequiredPersistentEntity(Class typeToRead) { - return relationalConverter.getMappingContext().getRequiredPersistentEntity(typeToRead); + return mappingContext.getRequiredPersistentEntity(typeToRead); } @Nullable private RelationalPersistentEntity getPersistentEntity(Class typeToRead) { - return relationalConverter.getMappingContext().getPersistentEntity(typeToRead); + return mappingContext.getPersistentEntity(typeToRead); + } + + private Object getWriteValue(PersistentPropertyAccessor propertyAccessor, RelationalPersistentProperty property) { + + TypeInformation type = property.getTypeInformation(); + Object value = relationalConverter.writeValue(propertyAccessor.getProperty(property), type); + + if (type.isCollectionLike()) { + + RelationalPersistentEntity nestedEntity = mappingContext + .getPersistentEntity(type.getRequiredActualType().getType()); + + if (nestedEntity != null) { + throw new InvalidDataAccessApiUsageException("Nested entities are not supported"); + } + + if (!dialect.supportsArrayColumns()) { + throw new InvalidDataAccessResourceUsageException( + "Dialect " + dialect.getClass().getName() + " does not support array columns"); + } + + if (!property.isArray()) { + Object zeroLengthArray = Array.newInstance(property.getActualType(), 0); + return relationalConverter.getConversionService().convert(value, zeroLengthArray.getClass()); + } + } + + return value; } /* diff --git a/src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java b/src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java index f3c8a9f..8b038e9 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java @@ -20,6 +20,7 @@ import io.r2dbc.spi.RowMetadata; import io.r2dbc.spi.Statement; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.BiFunction; @@ -49,6 +50,14 @@ public interface ReactiveDataAccessStrategy { */ List getValuesToInsert(Object object); + /** + * Returns a {@link Map} that maps column names to a {@link SettableValue} value. + * + * @param object must not be {@literal null}. + * @return + */ + Map getColumnsToUpdate(Object object); + /** * Map the {@link Sort} object to apply field name mapping using {@link Class the type to read}. * diff --git a/src/main/java/org/springframework/data/r2dbc/function/convert/MappingR2dbcConverter.java b/src/main/java/org/springframework/data/r2dbc/function/convert/MappingR2dbcConverter.java index 6b291e8..54b26ec 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/convert/MappingR2dbcConverter.java +++ b/src/main/java/org/springframework/data/r2dbc/function/convert/MappingR2dbcConverter.java @@ -21,7 +21,6 @@ import io.r2dbc.spi.RowMetadata; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Optional; import java.util.function.BiFunction; import org.springframework.core.convert.ConversionService; @@ -65,32 +64,6 @@ public class MappingR2dbcConverter { this.relationalConverter = converter; } - /** - * Returns a {@link Map} that maps column names to an {@link Optional} value. Used {@link Optional#empty()} if the - * underlying property is {@literal null}. - * - * @param object must not be {@literal null}. - * @return - */ - public Map getColumnsToUpdate(Object object) { - - Assert.notNull(object, "Entity object must not be null!"); - - Class userClass = ClassUtils.getUserClass(object); - RelationalPersistentEntity entity = getMappingContext().getRequiredPersistentEntity(userClass); - - Map update = new LinkedHashMap<>(); - - PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(object); - - for (RelationalPersistentProperty property : entity) { - update.put(property.getColumnName(), - new SettableValue(property.getColumnName(), propertyAccessor.getProperty(property), property.getType())); - } - - return update; - } - /** * Returns a {@link java.util.function.Function} that populates the id property of the {@code object} from a * {@link Row}. diff --git a/src/main/java/org/springframework/data/r2dbc/function/convert/R2dbcCustomConversions.java b/src/main/java/org/springframework/data/r2dbc/function/convert/R2dbcCustomConversions.java new file mode 100644 index 0000000..8c3a692 --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/function/convert/R2dbcCustomConversions.java @@ -0,0 +1,26 @@ +package org.springframework.data.r2dbc.function.convert; + +import java.util.Collection; + +import org.springframework.data.convert.CustomConversions; + +/** + * Value object to capture custom conversion. {@link R2dbcCustomConversions} also act as factory for + * {@link org.springframework.data.mapping.model.SimpleTypeHolder} + * + * @author Mark Paluch + * @see CustomConversions + * @see org.springframework.data.mapping.model.SimpleTypeHolder + */ +public class R2dbcCustomConversions extends CustomConversions { + + /** + * Creates a new {@link CustomConversions} instance registering the given converters. + * + * @param storeConversions must not be {@literal null}. + * @param converters must not be {@literal null}. + */ + public R2dbcCustomConversions(StoreConversions storeConversions, Collection converters) { + super(storeConversions, converters); + } +} diff --git a/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrar.java b/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrar.java index 5279e09..91949b3 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrar.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrar.java @@ -24,7 +24,6 @@ import org.springframework.data.repository.config.RepositoryConfigurationExtensi * R2DBC-specific {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar}. * * @author Mark Paluch - * @since 2.0 */ class R2dbcRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java b/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java index 4de641d..e7e37cc 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java @@ -70,7 +70,7 @@ public class SimpleR2dbcRepository implements ReactiveCrudRepository columns = converter.getColumnsToUpdate(objectToSave); + Map columns = accessStrategy.getColumnsToUpdate(objectToSave); columns.remove(getIdColumnName()); // do not update the Id column. String idColumnName = getIdColumnName(); BindIdOperation update = accessStrategy.updateById(entity.getTableName(), columns.keySet(), idColumnName); diff --git a/src/test/java/org/springframework/data/r2dbc/dialect/PostgresDialectUnitTests.java b/src/test/java/org/springframework/data/r2dbc/dialect/PostgresDialectUnitTests.java index 3b0168d..29f5d2c 100644 --- a/src/test/java/org/springframework/data/r2dbc/dialect/PostgresDialectUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/dialect/PostgresDialectUnitTests.java @@ -2,7 +2,11 @@ package org.springframework.data.r2dbc.dialect; import static org.assertj.core.api.Assertions.*; +import java.util.Collection; +import java.util.List; + import org.junit.Test; +import org.springframework.data.mapping.model.SimpleTypeHolder; /** * Unit tests for {@link PostgresDialect}. @@ -22,4 +26,21 @@ public class PostgresDialectUnitTests { assertThat(first.getPlaceholder()).isEqualTo("$1"); assertThat(second.getPlaceholder()).isEqualTo("$2"); } + + @Test // gh-30 + public void shouldConsiderCollectionTypesAsSimple() { + + SimpleTypeHolder holder = PostgresDialect.INSTANCE.getSimpleTypeHolder(); + + assertThat(holder.isSimpleType(List.class)).isTrue(); + assertThat(holder.isSimpleType(Collection.class)).isTrue(); + } + + @Test // gh-30 + public void shouldConsiderStringArrayTypeAsSimple() { + + SimpleTypeHolder holder = PostgresDialect.INSTANCE.getSimpleTypeHolder(); + + assertThat(holder.isSimpleType(String[].class)).isTrue(); + } } diff --git a/src/test/java/org/springframework/data/r2dbc/dialect/SqlServerDialectUnitTests.java b/src/test/java/org/springframework/data/r2dbc/dialect/SqlServerDialectUnitTests.java index 0e84801..1e96e38 100644 --- a/src/test/java/org/springframework/data/r2dbc/dialect/SqlServerDialectUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/dialect/SqlServerDialectUnitTests.java @@ -2,7 +2,10 @@ package org.springframework.data.r2dbc.dialect; import static org.assertj.core.api.Assertions.*; +import java.util.UUID; + import org.junit.Test; +import org.springframework.data.mapping.model.SimpleTypeHolder; /** * Unit tests for {@link SqlServerDialect}. @@ -22,4 +25,12 @@ public class SqlServerDialectUnitTests { assertThat(first.getPlaceholder()).isEqualTo("@P0"); assertThat(second.getPlaceholder()).isEqualTo("@P1_foobar"); } + + @Test // gh-30 + public void shouldConsiderUuidAsSimple() { + + SimpleTypeHolder holder = SqlServerDialect.INSTANCE.getSimpleTypeHolder(); + + assertThat(holder.isSimpleType(UUID.class)).isTrue(); + } } diff --git a/src/test/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategyUnitTests.java b/src/test/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategyUnitTests.java index df27994..a8e7a5d 100644 --- a/src/test/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategyUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategyUnitTests.java @@ -8,9 +8,12 @@ import io.r2dbc.spi.Statement; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; +import java.util.Map; import org.junit.Test; import org.springframework.data.r2dbc.dialect.PostgresDialect; +import org.springframework.data.r2dbc.function.convert.SettableValue; /** * Unit tests for {@link DefaultReactiveDataAccessStrategy}. @@ -101,4 +104,38 @@ public class DefaultReactiveDataAccessStrategyUnitTests { operation.bindId(statement, "bar"); assertThat(operation.toQuery()).isEqualTo("DELETE FROM table WHERE id IN ($1, $2)"); } + + @Test // gh-22 + public void shouldUpdateArray() { + + Map columnsToUpdate = strategy + .getColumnsToUpdate(new WithCollectionTypes(new String[] { "one", "two" }, null)); + + Object stringArray = columnsToUpdate.get("string_array").getValue(); + assertThat(stringArray).isInstanceOf(String[].class); + assertThat((String[]) stringArray).hasSize(2).contains("one", "two"); + } + + @Test // gh-22 + public void shouldConvertListToArray() { + + Map columnsToUpdate = strategy + .getColumnsToUpdate(new WithCollectionTypes(null, Arrays.asList("one", "two"))); + + Object stringArray = columnsToUpdate.get("string_collection").getValue(); + assertThat(stringArray).isInstanceOf(String[].class); + assertThat((String[]) stringArray).hasSize(2).contains("one", "two"); + } + + static class WithCollectionTypes { + + String[] stringArray; + + List stringCollection; + + WithCollectionTypes(String[] stringArray, List stringCollection) { + this.stringArray = stringArray; + this.stringCollection = stringCollection; + } + } } diff --git a/src/test/java/org/springframework/data/r2dbc/function/convert/EntityRowMapperUnitTests.java b/src/test/java/org/springframework/data/r2dbc/function/convert/EntityRowMapperUnitTests.java index a8ba3da..b17c080 100644 --- a/src/test/java/org/springframework/data/r2dbc/function/convert/EntityRowMapperUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/function/convert/EntityRowMapperUnitTests.java @@ -7,6 +7,8 @@ import io.r2dbc.spi.Row; import io.r2dbc.spi.RowMetadata; import lombok.RequiredArgsConstructor; +import java.util.List; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -57,6 +59,17 @@ public class EntityRowMapperUnitTests { assertThat(result.id).isEqualTo(36L); } + @Test // gh-30 + public void shouldConvertArrayToCollection() { + + EntityRowMapper mapper = getRowMapper(EntityWithCollection.class); + when(rowMock.get("ids")).thenReturn((new String[] { "foo", "bar" })); + + EntityWithCollection result = mapper.apply(rowMock, metadata); + assertThat(result.ids).contains("foo", "bar"); + } + + @SuppressWarnings("unchecked") private EntityRowMapper getRowMapper(Class type) { RelationalPersistentEntity entity = (RelationalPersistentEntity) strategy.getMappingContext() .getRequiredPersistentEntity(type); @@ -76,4 +89,8 @@ public class EntityRowMapperUnitTests { static class ConversionWithConstructorCreation { final long id; } + + static class EntityWithCollection { + List ids; + } }