diff --git a/pom.xml b/pom.xml index 87b7e95ebe..418eca247c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 1.2.0.BUILD-SNAPSHOT + 1.2.0.DATAJDBC-386-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index f2bbb72319..a932f78c27 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 1.2.0.BUILD-SNAPSHOT + 1.2.0.DATAJDBC-386-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index 0904eb8136..f6d2cf9cb1 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-data-jdbc - 1.2.0.BUILD-SNAPSHOT + 1.2.0.DATAJDBC-386-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 1.2.0.BUILD-SNAPSHOT + 1.2.0.DATAJDBC-386-SNAPSHOT diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java index 8b1c3ebef6..8a8c7ec57e 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java @@ -266,7 +266,7 @@ private Object getIdFrom(DbAction.WithEntity idOwningAction) { .getRequiredPersistentEntity(idOwningAction.getEntityType()); Object identifier = persistentEntity.getIdentifierAccessor(idOwningAction.getEntity()).getIdentifier(); - Assert.state(identifier != null, "Couldn't get obtain a required id value"); + Assert.state(identifier != null, "Couldn't obtain a required id value"); return identifier; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java index 5408f066c9..93aa5de4de 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java @@ -15,6 +15,9 @@ */ package org.springframework.data.jdbc.core; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.lang.Nullable; /** @@ -22,6 +25,7 @@ * * @author Jens Schauder * @author Thomas Lang + * @author Milan Milanov */ public interface JdbcAggregateOperations { @@ -128,4 +132,24 @@ public interface JdbcAggregateOperations { * @return whether the aggregate exists. */ boolean existsById(Object id, Class domainType); + + /** + * Load all aggregates of a given type, sorted. + * + * @param domainType the type of the aggregate roots. Must not be {@code null}. + * @param the type of the aggregate roots. Must not be {@code null}. + * @param sort the sorting information. Must not be {@code null}. + * @return Guaranteed to be not {@code null}. + */ + Iterable findAll(Class domainType, Sort sort); + + /** + * Load a page of (potentially sorted) aggregates of a given type. + * + * @param domainType the type of the aggregate roots. Must not be {@code null}. + * @param the type of the aggregate roots. Must not be {@code null}. + * @param pageable the pagination information. Must not be {@code null}. + * @return Guaranteed to be not {@code null}. + */ + Page findAll(Class domainType, Pageable pageable); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java index a27ea9272b..8f98c635ab 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java @@ -19,9 +19,15 @@ import java.util.List; import java.util.Optional; import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.mapping.IdentifierAccessor; import org.springframework.data.mapping.callback.EntityCallbacks; @@ -56,6 +62,7 @@ * @author Mark Paluch * @author Thomas Lang * @author Christoph Strobl + * @author Milan Milanov */ public class JdbcAggregateTemplate implements JdbcAggregateOperations { @@ -234,6 +241,35 @@ public boolean existsById(Object id, Class domainType) { return accessStrategy.existsById(id, domainType); } + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class, org.springframework.data.domain.Sort) + */ + @Override + public Iterable findAll(Class domainType, Sort sort) { + + Assert.notNull(domainType, "Domain type must not be null!"); + + Iterable all = accessStrategy.findAll(domainType, sort); + return triggerAfterLoad(all); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class, org.springframework.data.domain.Pageable) + */ + @Override + public Page findAll(Class domainType, Pageable pageable) { + + Assert.notNull(domainType, "Domain type must not be null!"); + + Iterable items = triggerAfterLoad(accessStrategy.findAll(domainType, pageable)); + long totalCount = accessStrategy.count(domainType); + + return new PageImpl<>(StreamSupport.stream(items.spliterator(), false).collect(Collectors.toList()), pageable, + totalCount); + } + /* * (non-Javadoc) * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java index 8cde089b9b..81e8cb1abf 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java @@ -37,12 +37,14 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.conversion.RelationalConverter; +import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.mapping.Embedded; import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.relational.domain.IdentifierProcessing; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; @@ -69,6 +71,7 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc private static final Converter, Map> ITERABLE_OF_ENTRY_TO_MAP_CONVERTER = new IterableOfEntryToMapConverter(); private final JdbcTypeFactory typeFactory; + private final IdentifierProcessing identifierProcessing = HsqlDbDialect.INSTANCE.getIdentifierProcessing(); private RelationResolver relationResolver; @@ -374,7 +377,8 @@ private Object readFrom(RelationalPersistentProperty property) { return readEntityFrom(property, path); } - Object value = getObjectFromResultSet(path.extendBy(property).getColumnAlias()); + Object value = getObjectFromResultSet( + path.extendBy(property).getColumnAlias().toColumnName(identifierProcessing)); return readValue(value, property.getTypeInformation()); } @@ -428,7 +432,8 @@ private Object readEntityFrom(RelationalPersistentProperty property, PersistentP if (idProperty != null) { idValue = newContext.readFrom(idProperty); } else { - idValue = newContext.getObjectFromResultSet(path.extendBy(property).getReverseColumnNameAlias()); + idValue = newContext.getObjectFromResultSet( + path.extendBy(property).getReverseColumnNameAlias().toColumnName(identifierProcessing)); } if (idValue == null) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CascadingDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CascadingDataAccessStrategy.java index 53ae324e7e..6e5be8b74f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CascadingDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CascadingDataAccessStrategy.java @@ -21,9 +21,12 @@ import java.util.function.Consumer; import java.util.function.Function; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.relational.domain.SqlIdentifier; /** * Delegates each methods to the {@link DataAccessStrategy}s passed to the constructor in turn until the first that does @@ -32,6 +35,7 @@ * @author Jens Schauder * @author Mark Paluch * @author Tyler Van Gorder + * @author Milan Milanov * @since 1.1 */ public class CascadingDataAccessStrategy implements DataAccessStrategy { @@ -47,7 +51,7 @@ public CascadingDataAccessStrategy(List strategies) { * @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map) */ @Override - public Object insert(T instance, Class domainType, Map additionalParameters) { + public Object insert(T instance, Class domainType, Map additionalParameters) { return collect(das -> das.insert(instance, domainType, additionalParameters)); } @@ -187,6 +191,24 @@ public boolean existsById(Object id, Class domainType) { return collect(das -> das.existsById(id, domainType)); } + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class, org.springframework.data.domain.Sort) + */ + @Override + public Iterable findAll(Class domainType, Sort sort) { + return collect(das -> das.findAll(domainType, sort)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class, org.springframework.data.domain.Pageable) + */ + @Override + public Iterable findAll(Class domainType, Pageable pageable) { + return collect(das -> das.findAll(domainType, pageable)); + } + private T collect(Function function) { // Keep as Eclipse fails to compile if <> is used. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java index 4284aa4d6c..93f5db53f7 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java @@ -18,10 +18,13 @@ import java.util.Map; import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.JdbcAggregateOperations; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.lang.Nullable; /** @@ -31,6 +34,7 @@ * * @author Jens Schauder * @author Tyler Van Gorder + * @author Milan Milanov */ public interface DataAccessStrategy extends RelationResolver { @@ -46,7 +50,8 @@ public interface DataAccessStrategy extends RelationResolver { * @deprecated since 1.1, use {@link #insert(Object, Class, Identifier)} instead. */ @Deprecated - Object insert(T instance, Class domainType, Map additionalParameters); + @Nullable + Object insert(T instance, Class domainType, Map additionalParameters); /** * Inserts a the data of a single entity. Referenced entities don't get handled. @@ -76,8 +81,8 @@ default Object insert(T instance, Class domainType, Identifier identifier boolean update(T instance, Class domainType); /** - * Updates the data of a single entity in the database and enforce optimistic record locking using the {@code previousVersion} - * property. Referenced entities don't get handled. + * Updates the data of a single entity in the database and enforce optimistic record locking using the + * {@code previousVersion} property. Referenced entities don't get handled. *

* The statement will be of the form : {@code UPDATE … SET … WHERE ID = :id and VERSION_COLUMN = :previousVersion } * and throw an optimistic record locking exception if no rows have been updated. @@ -87,7 +92,8 @@ default Object insert(T instance, Class domainType, Identifier identifier * @param previousVersion The previous version assigned to the instance being saved. * @param the type of the instance to save. * @return whether the update actually updated a row. - * @throws OptimisticLockingFailureException if the update fails to update at least one row assuming the the optimistic locking version check failed. + * @throws OptimisticLockingFailureException if the update fails to update at least one row assuming the the + * optimistic locking version check failed. * @since 2.0 */ boolean updateWithVersion(T instance, Class domainType, Number previousVersion); @@ -113,7 +119,8 @@ default Object insert(T instance, Class domainType, Identifier identifier * @param domainType the type of entity to be deleted. Implicitly determines the table to operate on. Must not be * {@code null}. * @param previousVersion The previous version assigned to the instance being saved. - * @throws OptimisticLockingFailureException if the update fails to update at least one row assuming the the optimistic locking version check failed. + * @throws OptimisticLockingFailureException if the update fails to update at least one row assuming the the + * optimistic locking version check failed. * @since 2.0 */ void deleteWithVersion(Object id, Class domainType, Number previousVersion); @@ -211,4 +218,24 @@ default Iterable findAllByPath(Identifier identifier, * @return {@code true} if a matching row exists, otherwise {@code false}. */ boolean existsById(Object id, Class domainType); + + /** + * Loads all entities of the given type, sorted. + * + * @param domainType the type of entities to load. Must not be {@code null}. + * @param the type of entities to load. + * @param sort the sorting information. Must not be {@code null}. + * @return Guaranteed to be not {@code null}. + */ + Iterable findAll(Class domainType, Sort sort); + + /** + * Loads all entities of the given type, paged and sorted. + * + * @param domainType the type of entities to load. Must not be {@code null}. + * @param the type of entities to load. + * @param pageable the pagination information. Must not be {@code null}. + * @return Guaranteed to be not {@code null}. + */ + Iterable findAll(Class domainType, Pageable pageable); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java index 04ae24ead8..6ce173d754 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java @@ -15,13 +15,11 @@ */ package org.springframework.data.jdbc.core.convert; -import static org.springframework.data.jdbc.core.convert.SqlGenerator.VERSION_SQL_PARAMETER_NAME; +import static org.springframework.data.jdbc.core.convert.SqlGenerator.*; import java.sql.JDBCType; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -31,20 +29,23 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.support.JdbcUtil; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyHandler; -import org.springframework.data.relational.core.conversion.RelationalEntityVersionUtils; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; 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.relational.domain.Identifier; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.JdbcUtils; import org.springframework.jdbc.support.KeyHolder; @@ -61,6 +62,7 @@ * @author Christoph Strobl * @author Tom Hombergs * @author Tyler Van Gorder + * @author Milan Milanov * @since 1.1 */ public class DefaultDataAccessStrategy implements DataAccessStrategy { @@ -98,7 +100,8 @@ public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, Relation * @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map) */ @Override - public Object insert(T instance, Class domainType, Map additionalParameters) { + @Nullable + public Object insert(T instance, Class domainType, Map additionalParameters) { return insert(instance, domainType, Identifier.from(additionalParameters)); } @@ -109,11 +112,11 @@ public Object insert(T instance, Class domainType, Map ad @Override public Object insert(T instance, Class domainType, Identifier identifier) { - KeyHolder holder = new GeneratedKeyHolder(); + SqlGenerator sqlGenerator = sql(domainType); RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); - MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "", - PersistentProperty::isIdProperty); + SqlIdentifierParameterSource parameterSource = getParameterSource(instance, persistentEntity, "", + PersistentProperty::isIdProperty, getIdentifierProcessing()); identifier.forEach((name, value, type) -> addConvertedPropertyValue(parameterSource, name, value, type)); @@ -124,8 +127,10 @@ public Object insert(T instance, Class domainType, Identifier identifier) addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName()); } + KeyHolder holder = new GeneratedKeyHolder(); + operations.update( // - sql(domainType).getInsert(new HashSet<>(Arrays.asList(parameterSource.getParameterNames()))), // + sqlGenerator.getInsert(new HashSet<>(parameterSource.getIdentifiers())), // parameterSource, // holder // ); @@ -142,7 +147,7 @@ public boolean update(S instance, Class domainType) { RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); return operations.update(sql(domainType).getUpdate(), - getParameterSource(instance, persistentEntity, "", Predicates.includeAll())) != 0; + getParameterSource(instance, persistentEntity, "", Predicates.includeAll(), getIdentifierProcessing())) != 0; } /* @@ -155,13 +160,14 @@ public boolean updateWithVersion(S instance, Class domainType, Number pre RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); // Adjust update statement to set the new version and use the old version in where clause. - MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "", - Predicates.includeAll()); - parameterSource.addValue(VERSION_SQL_PARAMETER_NAME, previousVersion); + SqlIdentifierParameterSource parameterSource = getParameterSource(instance, persistentEntity, "", + Predicates.includeAll(), getIdentifierProcessing()); + parameterSource.addValue(VERSION_SQL_PARAMETER, previousVersion); int affectedRows = operations.update(sql(domainType).getUpdateWithVersion(), parameterSource); if (affectedRows == 0) { + throw new OptimisticLockingFailureException( String.format("Optimistic lock exception on saving entity of type %s.", persistentEntity.getName())); } @@ -177,7 +183,7 @@ public boolean updateWithVersion(S instance, Class domainType, Number pre public void delete(Object id, Class domainType) { String deleteByIdSql = sql(domainType).getDeleteById(); - MapSqlParameterSource parameter = createIdParameterSource(id, domainType); + SqlParameterSource parameter = createIdParameterSource(id, domainType); operations.update(deleteByIdSql, parameter); } @@ -193,8 +199,8 @@ public void deleteWithVersion(Object id, Class domainType, Number previou RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType); - MapSqlParameterSource parameterSource = createIdParameterSource(id, domainType); - parameterSource.addValue(VERSION_SQL_PARAMETER_NAME, previousVersion); + SqlIdentifierParameterSource parameterSource = createIdParameterSource(id, domainType); + parameterSource.addValue(VERSION_SQL_PARAMETER, previousVersion); int affectedRows = operations.update(sql(domainType).getDeleteByIdAndVersion(), parameterSource); if (affectedRows == 0) { @@ -216,11 +222,11 @@ public void delete(Object rootId, PersistentPropertyPath parameters = new HashMap<>(); - parameters.put("rootId", rootId); - operations.update(format, parameters); + SqlIdentifierParameterSource parameters = new SqlIdentifierParameterSource(getIdentifierProcessing()); + parameters.addValue(ROOT_ID_PARAMETER, rootId); + operations.update(delete, parameters); } /* @@ -265,7 +271,7 @@ public long count(Class domainType) { public T findById(Object id, Class domainType) { String findOneSql = sql(domainType).getFindOne(); - MapSqlParameterSource parameter = createIdParameterSource(id, domainType); + SqlIdentifierParameterSource parameter = createIdParameterSource(id, domainType); try { return operations.queryForObject(findOneSql, parameter, (RowMapper) getEntityRowMapper(domainType)); @@ -297,9 +303,9 @@ public Iterable findAllById(Iterable ids, Class domainType) { } RelationalPersistentProperty idProperty = getRequiredPersistentEntity(domainType).getRequiredIdProperty(); - MapSqlParameterSource parameterSource = new MapSqlParameterSource(); + SqlIdentifierParameterSource parameterSource = new SqlIdentifierParameterSource(getIdentifierProcessing()); - addConvertedPropertyValuesAsList(parameterSource, idProperty, ids, "ids"); + addConvertedPropertyValuesAsList(parameterSource, idProperty, ids, IDS_SQL_PARAMETER); String findAllInListSql = sql(domainType).getFindAllInList(); @@ -324,12 +330,20 @@ public Iterable findAllByPath(Identifier identifier, String findAllByProperty = sql(actualType) // .getFindAllByProperty(identifier, path.getQualifierColumn(), path.isOrdered()); - MapSqlParameterSource parameters = new MapSqlParameterSource(identifier.toMap()); - RowMapper rowMapper = path.isMap() ? this.getMapEntityRowMapper(path, identifier) : this.getEntityRowMapper(path, identifier); - return operations.query(findAllByProperty, parameters, (RowMapper) rowMapper); + return operations.query(findAllByProperty, createParameterSource(identifier, getIdentifierProcessing()), + (RowMapper) rowMapper); + } + + private SqlParameterSource createParameterSource(Identifier identifier, IdentifierProcessing identifierProcessing) { + + SqlIdentifierParameterSource parameterSource = new SqlIdentifierParameterSource(identifierProcessing); + + identifier.toMap().forEach(parameterSource::addValue); + + return parameterSource; } /* @@ -355,7 +369,7 @@ public Iterable findAllByProperty(Object rootId, RelationalPersistentPro public boolean existsById(Object id, Class domainType) { String existsSql = sql(domainType).getExists(); - MapSqlParameterSource parameter = createIdParameterSource(id, domainType); + SqlParameterSource parameter = createIdParameterSource(id, domainType); Boolean result = operations.queryForObject(existsSql, parameter, Boolean.class); Assert.state(result != null, "The result of an exists query must not be null"); @@ -363,10 +377,31 @@ public boolean existsById(Object id, Class domainType) { return result; } - private MapSqlParameterSource getParameterSource(@Nullable S instance, RelationalPersistentEntity persistentEntity, - String prefix, Predicate skipProperty) { + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class, org.springframework.data.domain.Sort) + */ + @Override + @SuppressWarnings("unchecked") + public Iterable findAll(Class domainType, Sort sort) { + return operations.query(sql(domainType).getFindAll(sort), (RowMapper) getEntityRowMapper(domainType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class, org.springframework.data.domain.Pageable) + */ + @Override + @SuppressWarnings("unchecked") + public Iterable findAll(Class domainType, Pageable pageable) { + return operations.query(sql(domainType).getFindAll(pageable), (RowMapper) getEntityRowMapper(domainType)); + } + + private SqlIdentifierParameterSource getParameterSource(@Nullable S instance, + RelationalPersistentEntity persistentEntity, String prefix, + Predicate skipProperty, IdentifierProcessing identifierProcessing) { - MapSqlParameterSource parameters = new MapSqlParameterSource(); + SqlIdentifierParameterSource parameters = new SqlIdentifierParameterSource(identifierProcessing); PersistentPropertyAccessor propertyAccessor = instance != null ? persistentEntity.getPropertyAccessor(instance) : NoValuePropertyAccessor.instance(); @@ -384,13 +419,14 @@ private MapSqlParameterSource getParameterSource(@Nullable S instance, Re Object value = propertyAccessor.getProperty(property); RelationalPersistentEntity embeddedEntity = context.getPersistentEntity(property.getType()); - MapSqlParameterSource additionalParameters = getParameterSource((T) value, - (RelationalPersistentEntity) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipProperty); - parameters.addValues(additionalParameters.getValues()); + SqlIdentifierParameterSource additionalParameters = getParameterSource((T) value, + (RelationalPersistentEntity) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipProperty, + identifierProcessing); + parameters.addAll(additionalParameters); } else { Object value = propertyAccessor.getProperty(property); - String paramName = prefix + property.getColumnName(); + SqlIdentifier paramName = property.getColumnName().prefix(prefix); addConvertedPropertyValue(parameters, property, value, paramName); } @@ -434,7 +470,7 @@ private Object getIdFromHolder(KeyHolder holder, RelationalPersistentEntity< return null; } - return keys.get(persistentEntity.getIdColumn()); + return keys.get(persistentEntity.getIdColumn().toColumnName(getIdentifierProcessing())); } } @@ -448,55 +484,58 @@ private EntityRowMapper getEntityRowMapper(PersistentPropertyPathExtension pa private RowMapper getMapEntityRowMapper(PersistentPropertyPathExtension path, Identifier identifier) { - String keyColumn = path.getQualifierColumn(); + SqlIdentifier keyColumn = path.getQualifierColumn(); Assert.notNull(keyColumn, () -> "KeyColumn must not be null for " + path); - return new MapEntityRowMapper<>(path, converter, identifier, keyColumn); + return new MapEntityRowMapper<>(path, converter, identifier, keyColumn, getIdentifierProcessing()); } - private MapSqlParameterSource createIdParameterSource(Object id, Class domainType) { + private SqlIdentifierParameterSource createIdParameterSource(Object id, Class domainType) { - MapSqlParameterSource parameterSource = new MapSqlParameterSource(); + SqlIdentifierParameterSource parameterSource = new SqlIdentifierParameterSource(getIdentifierProcessing()); addConvertedPropertyValue( // parameterSource, // getRequiredPersistentEntity(domainType).getRequiredIdProperty(), // id, // - "id" // + ID_SQL_PARAMETER // ); return parameterSource; } - private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, RelationalPersistentProperty property, - @Nullable Object value, String paramName) { + private IdentifierProcessing getIdentifierProcessing() { + return sqlGeneratorSource.getDialect().getIdentifierProcessing(); + } - JdbcValue jdbcValue = converter.writeJdbcValue( // - value, // - property.getColumnType(), // - property.getSqlType() // - ); + private void addConvertedPropertyValue(SqlIdentifierParameterSource parameterSource, + RelationalPersistentProperty property, @Nullable Object value, SqlIdentifier name) { - parameterSource.addValue(paramName, jdbcValue.getValue(), JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType())); + addConvertedValue(parameterSource, value, name, property.getColumnType(), property.getSqlType()); } - private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, String name, Object value, - Class type) { + private void addConvertedPropertyValue(SqlIdentifierParameterSource parameterSource, SqlIdentifier name, Object value, + Class javaType) { + + addConvertedValue(parameterSource, value, name, javaType, JdbcUtil.sqlTypeFor(javaType)); + } + + private void addConvertedValue(SqlIdentifierParameterSource parameterSource, @Nullable Object value, + SqlIdentifier paramName, Class javaType, int sqlType) { JdbcValue jdbcValue = converter.writeJdbcValue( // value, // - type, // - JdbcUtil.sqlTypeFor(type) // + javaType, // + sqlType // ); parameterSource.addValue( // - name, // + paramName, // jdbcValue.getValue(), // - JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType()) // - ); + JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType())); } - private void addConvertedPropertyValuesAsList(MapSqlParameterSource parameterSource, - RelationalPersistentProperty property, Iterable values, String paramName) { + private void addConvertedPropertyValuesAsList(SqlIdentifierParameterSource parameterSource, + RelationalPersistentProperty property, Iterable values, SqlIdentifier paramName) { List convertedIds = new ArrayList<>(); JdbcValue jdbcValue = null; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java index 7996ac4b30..9a797a80ec 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java @@ -17,9 +17,12 @@ import java.util.Map; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.util.Assert; /** @@ -28,6 +31,7 @@ * * @author Jens Schauder * @author Tyler Van Gorder + * @author Milan Milanov * @since 1.1 */ public class DelegatingDataAccessStrategy implements DataAccessStrategy { @@ -39,7 +43,7 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy { * @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map) */ @Override - public Object insert(T instance, Class domainType, Map additionalParameters) { + public Object insert(T instance, Class domainType, Map additionalParameters) { return delegate.insert(instance, domainType, additionalParameters); } @@ -70,6 +74,7 @@ public boolean updateWithVersion(S instance, Class domainType, Number nex return delegate.updateWithVersion(instance, domainType, nextVersion); } + /* * (non-Javadoc) * @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, org.springframework.data.mapping.PersistentPropertyPath) @@ -185,6 +190,24 @@ public boolean existsById(Object id, Class domainType) { return delegate.existsById(id, domainType); } + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class, org.springframework.data.domain.Sort) + */ + @Override + public Iterable findAll(Class domainType, Sort sort) { + return delegate.findAll(domainType, sort); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class, org.springframework.data.domain.Pageable) + */ + @Override + public Iterable findAll(Class domainType, Pageable pageable) { + return delegate.findAll(domainType, pageable); + } + /** * Must be called exactly once before calling any of the other methods. * diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java index 1cae866dc8..51f038e1b1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java @@ -24,6 +24,8 @@ import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.jdbc.core.RowMapper; import org.springframework.lang.NonNull; @@ -40,13 +42,14 @@ class MapEntityRowMapper implements RowMapper> { private final PersistentPropertyPathExtension path; private final JdbcConverter converter; private final Identifier identifier; - - private final String keyColumn; + private final SqlIdentifier keyColumn; + private final IdentifierProcessing identifierProcessing; @NonNull @Override public Map.Entry mapRow(ResultSet rs, int rowNum) throws SQLException { - Object key = rs.getObject(keyColumn); + + Object key = rs.getObject(keyColumn.toColumnName(identifierProcessing)); return new HashMap.SimpleEntry<>(key, mapEntity(rs, key)); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlContext.java index 005da10963..654ff40051 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlContext.java @@ -20,6 +20,8 @@ import org.springframework.data.relational.core.sql.Column; import org.springframework.data.relational.core.sql.SQL; import org.springframework.data.relational.core.sql.Table; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.SqlIdentifier; /** * Utility to get from path to SQL DSL elements. @@ -33,36 +35,41 @@ class SqlContext { private final RelationalPersistentEntity entity; private final Table table; + private final IdentifierProcessing identifierProcessing; - SqlContext(RelationalPersistentEntity entity) { + SqlContext(RelationalPersistentEntity entity, IdentifierProcessing identifierProcessing) { + + this.identifierProcessing = identifierProcessing; this.entity = entity; - this.table = SQL.table(entity.getTableName()); + this.table = SQL.table(entity.getTableName().toSql(this.identifierProcessing)); } Column getIdColumn() { - return table.column(entity.getIdColumn()); + return table.column(entity.getIdColumn().toSql(identifierProcessing)); } Column getVersionColumn() { - return table.column(entity.getRequiredVersionProperty().getColumnName()); + return table.column(entity.getRequiredVersionProperty().getColumnName().toSql(identifierProcessing)); } - + Table getTable() { return table; } Table getTable(PersistentPropertyPathExtension path) { - String tableAlias = path.getTableAlias(); - Table table = SQL.table(path.getTableName()); - return tableAlias == null ? table : table.as(tableAlias); + SqlIdentifier tableAlias = path.getTableAlias(); + Table table = SQL.table(path.getTableName().toSql(identifierProcessing)); + return tableAlias == null ? table : table.as(tableAlias.toSql(identifierProcessing)); } Column getColumn(PersistentPropertyPathExtension path) { - return getTable(path).column(path.getColumnName()).as(path.getColumnAlias()); + return getTable(path).column(path.getColumnName().toSql(identifierProcessing)) + .as(path.getColumnAlias().toSql(identifierProcessing)); } Column getReverseColumn(PersistentPropertyPathExtension path) { - return getTable(path).column(path.getReverseColumnName()).as(path.getReverseColumnNameAlias()); + return getTable(path).column(path.getReverseColumnName().toSql(identifierProcessing)) + .as(path.getReverseColumnNameAlias().toSql(identifierProcessing)); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java index 3dc1bcd5e9..69f092b47b 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java @@ -17,22 +17,19 @@ import lombok.Value; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.RenderContextFactory; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; @@ -40,6 +37,8 @@ import org.springframework.data.relational.core.sql.*; import org.springframework.data.relational.core.sql.render.SqlRenderer; import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.data.util.Lazy; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -54,16 +53,22 @@ * @author Mark Paluch * @author Tom Hombergs * @author Tyler Van Gorder + * @author Milan Milanov */ class SqlGenerator { - static final String VERSION_SQL_PARAMETER_NAME = "___oldOptimisticLockingVersion"; + static final SqlIdentifier VERSION_SQL_PARAMETER = SqlIdentifier.unquoted("___oldOptimisticLockingVersion"); + static final SqlIdentifier ID_SQL_PARAMETER = SqlIdentifier.unquoted("id"); + static final SqlIdentifier IDS_SQL_PARAMETER = SqlIdentifier.unquoted("ids"); + static final SqlIdentifier ROOT_ID_PARAMETER = SqlIdentifier.unquoted("rootId"); private static final Pattern parameterPattern = Pattern.compile("\\W"); private final RelationalPersistentEntity entity; private final MappingContext, RelationalPersistentProperty> mappingContext; + private final IdentifierProcessing identifierProcessing; private final SqlContext sqlContext; + private final SqlRenderer sqlRenderer; private final Columns columns; private final Lazy findOneSql = Lazy.of(this::createFindOneSql); @@ -82,15 +87,18 @@ class SqlGenerator { /** * Create a new {@link SqlGenerator} given {@link RelationalMappingContext} and {@link RelationalPersistentEntity}. - * + * * @param mappingContext must not be {@literal null}. * @param entity must not be {@literal null}. + * @param dialect must not be {@literal null}. */ - SqlGenerator(RelationalMappingContext mappingContext, RelationalPersistentEntity entity) { + SqlGenerator(RelationalMappingContext mappingContext, RelationalPersistentEntity entity, Dialect dialect) { this.mappingContext = mappingContext; this.entity = entity; - this.sqlContext = new SqlContext(entity); + this.identifierProcessing = dialect.getIdentifierProcessing(); + this.sqlRenderer = SqlRenderer.create(new RenderContextFactory(dialect).createRenderContext()); + this.sqlContext = new SqlContext(entity, identifierProcessing); this.columns = new Columns(entity, mappingContext); } @@ -103,7 +111,7 @@ class SqlGenerator { * @param filterColumn the column to apply the IN-condition to. * @return the IN condition */ - private static Condition getSubselectCondition(PersistentPropertyPathExtension path, + private Condition getSubselectCondition(PersistentPropertyPathExtension path, Function rootCondition, Column filterColumn) { PersistentPropertyPathExtension parentPath = path.getParentPath(); @@ -115,9 +123,10 @@ private static Condition getSubselectCondition(PersistentPropertyPathExtension p return rootCondition.apply(filterColumn); } - Table subSelectTable = SQL.table(parentPath.getTableName()); - Column idColumn = subSelectTable.column(parentPath.getIdColumnName()); - Column selectFilterColumn = subSelectTable.column(parentPath.getEffectiveIdColumnName()); + Table subSelectTable = SQL.table(parentPath.getTableName().toSql(identifierProcessing)); + Column idColumn = subSelectTable.column(parentPath.getIdColumnName().toSql(identifierProcessing)); + Column selectFilterColumn = subSelectTable + .column(parentPath.getEffectiveIdColumnName().toSql(identifierProcessing)); Condition innerCondition; @@ -139,8 +148,8 @@ private static Condition getSubselectCondition(PersistentPropertyPathExtension p return filterColumn.in(select); } - private static BindMarker getBindMarker(String columnName) { - return SQL.bindMarker(":" + parameterPattern.matcher(columnName).replaceAll("")); + private BindMarker getBindMarker(SqlIdentifier columnName) { + return SQL.bindMarker(":" + parameterPattern.matcher(columnName.toColumnName(identifierProcessing)).replaceAll("")); } /** @@ -162,6 +171,26 @@ String getFindAll() { return findAllSql.get(); } + /** + * Returns a query for selecting all simple properties of an entity, including those for one-to-one relationships, + * sorted by the given parameter. + * + * @return a SQL statement. Guaranteed to be not {@code null}. + */ + String getFindAll(Sort sort) { + return render(selectBuilder(Collections.emptyList(), sort, Pageable.unpaged()).build()); + } + + /** + * Returns a query for selecting all simple properties of an entity, including those for one-to-one relationships, + * paged and sorted by the given parameter. + * + * @return a SQL statement. Guaranteed to be not {@code null}. + */ + String getFindAll(Pageable pageable) { + return render(selectBuilder(Collections.emptyList(), pageable.getSort(), pageable).build()); + } + /** * Returns a query for selecting all simple properties of an entity, including those for one-to-one relationships. * Results are limited to those rows referencing some other entity using the column specified by @@ -174,7 +203,7 @@ String getFindAll() { * keyColumn must not be {@code null}. * @return a SQL String. */ - String getFindAllByProperty(Identifier parentIdentifier, @Nullable String keyColumn, boolean ordered) { + String getFindAllByProperty(Identifier parentIdentifier, @Nullable SqlIdentifier keyColumn, boolean ordered) { Assert.isTrue(keyColumn != null || !ordered, "If the SQL statement should be ordered a keyColumn to order by must be provided."); @@ -182,7 +211,7 @@ String getFindAllByProperty(Identifier parentIdentifier, @Nullable String keyCol SelectBuilder.SelectWhere builder = selectBuilder( // keyColumn == null // ? Collections.emptyList() // - : Collections.singleton(keyColumn) // + : Collections.singleton(keyColumn.toSql(identifierProcessing)) // ); Table table = getTable(); @@ -191,7 +220,9 @@ String getFindAllByProperty(Identifier parentIdentifier, @Nullable String keyCol SelectBuilder.SelectWhereAndOr withWhereClause = builder.where(condition); Select select = ordered // - ? withWhereClause.orderBy(table.column(keyColumn).as(keyColumn)).build() // + ? withWhereClause + .orderBy(table.column(keyColumn.toSql(identifierProcessing)).as(keyColumn.toSql(identifierProcessing))) + .build() // : withWhereClause.build(); return render(select); @@ -200,9 +231,10 @@ String getFindAllByProperty(Identifier parentIdentifier, @Nullable String keyCol private Condition buildConditionForBackReference(Identifier parentIdentifier, Table table) { Condition condition = null; - for (String backReferenceColumn : parentIdentifier.toMap().keySet()) { + for (SqlIdentifier backReferenceColumn : parentIdentifier.toMap().keySet()) { - Condition newCondition = table.column(backReferenceColumn).isEqualTo(getBindMarker(backReferenceColumn)); + Condition newCondition = table.column(backReferenceColumn.toSql(identifierProcessing)) + .isEqualTo(getBindMarker(backReferenceColumn)); condition = condition == null ? newCondition : condition.and(newCondition); } @@ -214,7 +246,7 @@ private Condition buildConditionForBackReference(Identifier parentIdentifier, Ta /** * Create a {@code SELECT COUNT(id) FROM … WHERE :id = …} statement. * - * @return + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ String getExists() { return existsSql.get(); @@ -223,7 +255,7 @@ String getExists() { /** * Create a {@code SELECT … FROM … WHERE :id = …} statement. * - * @return + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ String getFindOne() { return findOneSql.get(); @@ -232,16 +264,16 @@ String getFindOne() { /** * Create a {@code INSERT INTO … (…) VALUES(…)} statement. * - * @return + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ - String getInsert(Set additionalColumns) { + String getInsert(Set additionalColumns) { return createInsertSql(additionalColumns); } /** * Create a {@code UPDATE … SET …} statement. * - * @return + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ String getUpdate() { return updateSql.get(); @@ -250,7 +282,7 @@ String getUpdate() { /** * Create a {@code UPDATE … SET … WHERE ID = :id and VERSION_COLUMN = :___oldOptimisticLockingVersion } statement. * - * @return + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ String getUpdateWithVersion() { return updateWithVersionSql.get(); @@ -259,7 +291,7 @@ String getUpdateWithVersion() { /** * Create a {@code SELECT COUNT(*) FROM …} statement. * - * @return + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ String getCount() { return countSql.get(); @@ -268,7 +300,7 @@ String getCount() { /** * Create a {@code DELETE FROM … WHERE :id = …} statement. * - * @return + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ String getDeleteById() { return deleteByIdSql.get(); @@ -277,7 +309,7 @@ String getDeleteById() { /** * Create a {@code DELETE FROM … WHERE :id = … and :___oldOptimisticLockingVersion = ...} statement. * - * @return + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ String getDeleteByIdAndVersion() { return deleteByIdAndVersionSql.get(); @@ -286,7 +318,7 @@ String getDeleteByIdAndVersion() { /** * Create a {@code DELETE FROM … WHERE :ids in (…)} statement. * - * @return + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ String getDeleteByList() { return deleteByListSql.get(); @@ -296,7 +328,7 @@ String getDeleteByList() { * Create a {@code DELETE} query and optionally filter by {@link PersistentPropertyPath}. * * @param path can be {@literal null}. - * @return + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ String createDeleteAllSql(@Nullable PersistentPropertyPath path) { @@ -315,16 +347,16 @@ String createDeleteAllSql(@Nullable PersistentPropertyPath path) { return createDeleteByPathAndCriteria(new PersistentPropertyPathExtension(mappingContext, path), - filterColumn -> filterColumn.isEqualTo(getBindMarker("rootId"))); + filterColumn -> filterColumn.isEqualTo(getBindMarker(ROOT_ID_PARAMETER))); } private String createFindOneSql() { - Select select = selectBuilder().where(getIdColumn().isEqualTo(getBindMarker("id"))) // + Select select = selectBuilder().where(getIdColumn().isEqualTo(getBindMarker(ID_SQL_PARAMETER))) // .build(); return render(select); @@ -376,11 +408,32 @@ private SelectBuilder.SelectWhere selectBuilder(Collection keyColumns) { return (SelectBuilder.SelectWhere) baseSelect; } + private SelectBuilder.SelectOrdered selectBuilder(Collection keyColumns, Sort sort, Pageable pageable) { + SelectBuilder.SelectWhere baseSelect = this.selectBuilder(keyColumns); + + if (baseSelect instanceof SelectBuilder.SelectFromAndJoin) { + if (pageable.isPaged()) { + return ((SelectBuilder.SelectFromAndJoin) baseSelect).limitOffset(pageable.getPageSize(), pageable.getOffset()) + .orderBy(extractOrderByFields(sort)); + } + return ((SelectBuilder.SelectFromAndJoin) baseSelect).orderBy(extractOrderByFields(sort)); + + } else if (baseSelect instanceof SelectBuilder.SelectFromAndJoinCondition) { + if (pageable.isPaged()) { + return ((SelectBuilder.SelectFromAndJoinCondition) baseSelect) + .limitOffset(pageable.getPageSize(), pageable.getOffset()).orderBy(extractOrderByFields(sort)); + } + return baseSelect.orderBy(extractOrderByFields(sort)); + } else { + throw new RuntimeException(); + } + } + /** * Create a {@link Column} for {@link PersistentPropertyPathExtension}. * - * @param path - * @return + * @param path the path to the column in question. + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ @Nullable Column getColumn(PersistentPropertyPathExtension path) { @@ -425,14 +478,14 @@ Join getJoin(PersistentPropertyPathExtension path) { return new Join( // currentTable, // - currentTable.column(path.getReverseColumnName()), // - parentTable.column(idDefiningParentPath.getIdColumnName()) // + currentTable.column(path.getReverseColumnName().toSql(identifierProcessing)), // + parentTable.column(idDefiningParentPath.getIdColumnName().toSql(identifierProcessing)) // ); } private String createFindAllInListSql() { - Select select = selectBuilder().where(getIdColumn().in(getBindMarker("ids"))).build(); + Select select = selectBuilder().where(getIdColumn().in(getBindMarker(IDS_SQL_PARAMETER))).build(); return render(select); } @@ -444,7 +497,7 @@ private String createExistsSql() { Select select = StatementBuilder // .select(Functions.count(getIdColumn())) // .from(table) // - .where(getIdColumn().isEqualTo(getBindMarker("id"))) // + .where(getIdColumn().isEqualTo(getBindMarker(ID_SQL_PARAMETER))) // .build(); return render(select); @@ -462,21 +515,22 @@ private String createCountSql() { return render(select); } - private String createInsertSql(Set additionalColumns) { + private String createInsertSql(Set additionalColumns) { Table table = getTable(); - Set columnNamesForInsert = new LinkedHashSet<>(columns.getInsertableColumns()); + Set columnNamesForInsert = new TreeSet<>(Comparator.comparing(id -> id.toSql(identifierProcessing))); + columnNamesForInsert.addAll(columns.getInsertableColumns()); columnNamesForInsert.addAll(additionalColumns); InsertBuilder.InsertIntoColumnsAndValuesWithBuild insert = Insert.builder().into(table); - for (String cn : columnNamesForInsert) { - insert = insert.column(table.column(cn)); + for (SqlIdentifier cn : columnNamesForInsert) { + insert = insert.column(table.column(cn.toSql(identifierProcessing))); } InsertBuilder.InsertValuesWithBuild insertWithValues = null; - for (String cn : columnNamesForInsert) { + for (SqlIdentifier cn : columnNamesForInsert) { insertWithValues = (insertWithValues == null ? insert : insertWithValues).values(getBindMarker(cn)); } @@ -490,7 +544,8 @@ private String createUpdateSql() { private String createUpdateWithVersionSql() { Update update = createBaseUpdate() // - .and(getVersionColumn().isEqualTo(SQL.bindMarker(":" + VERSION_SQL_PARAMETER_NAME))) // + .and(getVersionColumn() + .isEqualTo(SQL.bindMarker(":" + VERSION_SQL_PARAMETER.toColumnName(identifierProcessing)))) // .build(); return render(update); @@ -503,7 +558,7 @@ private UpdateBuilder.UpdateWhereAndOr createBaseUpdate() { List assignments = columns.getUpdateableColumns() // .stream() // .map(columnName -> Assignments.value( // - table.column(columnName), // + table.column(columnName.toSql(identifierProcessing)), // getBindMarker(columnName))) // .collect(Collectors.toList()); @@ -520,7 +575,8 @@ private String createDeleteSql() { private String createDeleteByIdAndVersionSql() { Delete delete = createBaseDeleteById(getTable()) // - .and(getVersionColumn().isEqualTo(SQL.bindMarker(":" + VERSION_SQL_PARAMETER_NAME))) // + .and(getVersionColumn() + .isEqualTo(SQL.bindMarker(":" + VERSION_SQL_PARAMETER.toColumnName(identifierProcessing)))) // .build(); return render(delete); @@ -533,13 +589,13 @@ private DeleteBuilder.DeleteWhereAndOr createBaseDeleteById(Table table) { private String createDeleteByPathAndCriteria(PersistentPropertyPathExtension path, Function rootCondition) { - Table table = SQL.table(path.getTableName()); + Table table = SQL.table(path.getTableName().toSql(identifierProcessing)); DeleteBuilder.DeleteWhere builder = Delete.builder() // .from(table); Delete delete; - Column filterColumn = table.column(path.getReverseColumnName()); + Column filterColumn = table.column(path.getReverseColumnName().toSql(identifierProcessing)); if (path.getLength() == 1) { @@ -561,26 +617,26 @@ private String createDeleteByListSql() { Delete delete = Delete.builder() // .from(table) // - .where(getIdColumn().in(getBindMarker("ids"))) // + .where(getIdColumn().in(getBindMarker(IDS_SQL_PARAMETER))) // .build(); return render(delete); } private String render(Select select) { - return SqlRenderer.create().render(select); + return this.sqlRenderer.render(select); } private String render(Insert insert) { - return SqlRenderer.create().render(insert); + return this.sqlRenderer.render(insert); } private String render(Update update) { - return SqlRenderer.create().render(update); + return this.sqlRenderer.render(update); } private String render(Delete delete) { - return SqlRenderer.create().render(delete); + return this.sqlRenderer.render(delete); } private Table getTable() { @@ -595,6 +651,12 @@ private Column getVersionColumn() { return sqlContext.getVersionColumn(); } + private List extractOrderByFields(Sort sort) { + return sort.stream() + .map(order -> OrderByField.from(Column.create(order.getProperty(), this.getTable()), order.getDirection())) + .collect(Collectors.toList()); + } + /** * Value object representing a {@code JOIN} association. */ @@ -609,17 +671,18 @@ static class Join { * Value object encapsulating column name caches. * * @author Mark Paluch + * @author Jens Schauder */ static class Columns { private final MappingContext, RelationalPersistentProperty> mappingContext; - private final List columnNames = new ArrayList<>(); - private final List idColumnNames = new ArrayList<>(); - private final List nonIdColumnNames = new ArrayList<>(); - private final Set readOnlyColumnNames = new HashSet<>(); - private final Set insertableColumns; - private final Set updateableColumns; + private final List columnNames = new ArrayList<>(); + private final List idColumnNames = new ArrayList<>(); + private final List nonIdColumnNames = new ArrayList<>(); + private final Set readOnlyColumnNames = new HashSet<>(); + private final Set insertableColumns; + private final Set updateableColumns; Columns(RelationalPersistentEntity entity, MappingContext, RelationalPersistentProperty> mappingContext) { @@ -628,12 +691,12 @@ static class Columns { populateColumnNameCache(entity, ""); - Set insertable = new LinkedHashSet<>(nonIdColumnNames); + Set insertable = new LinkedHashSet<>(nonIdColumnNames); insertable.removeAll(readOnlyColumnNames); this.insertableColumns = Collections.unmodifiableSet(insertable); - Set updateable = new LinkedHashSet<>(columnNames); + Set updateable = new LinkedHashSet<>(columnNames); updateable.removeAll(idColumnNames); updateable.removeAll(readOnlyColumnNames); @@ -656,7 +719,7 @@ private void populateColumnNameCache(RelationalPersistentEntity entity, Strin private void initSimpleColumnName(RelationalPersistentProperty property, String prefix) { - String columnName = prefix + property.getColumnName(); + SqlIdentifier columnName = property.getColumnName().prefix(prefix); columnNames.add(columnName); @@ -684,14 +747,14 @@ private void initEmbeddedColumnNames(RelationalPersistentProperty property, Stri /** * @return Column names that can be used for {@code INSERT}. */ - Set getInsertableColumns() { + Set getInsertableColumns() { return insertableColumns; } /** * @return Column names that can be used for {@code UPDATE}. */ - Set getUpdateableColumns() { + Set getUpdateableColumns() { return updateableColumns; } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGeneratorSource.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGeneratorSource.java index 4ea4752917..12fa253275 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGeneratorSource.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGeneratorSource.java @@ -19,6 +19,7 @@ import java.util.Map; +import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.util.ConcurrentReferenceHashMap; @@ -27,14 +28,25 @@ * domain type, the same generator will get returned. * * @author Jens Schauder + * @author Milan Milanov */ @RequiredArgsConstructor public class SqlGeneratorSource { private final Map, SqlGenerator> CACHE = new ConcurrentReferenceHashMap<>(); private final RelationalMappingContext context; + private final Dialect dialect; + + /** + * @return the {@link Dialect} used by the created {@link SqlGenerator} instances. Guaranteed to be not + * {@literal null}. + */ + public Dialect getDialect() { + return dialect; + } SqlGenerator getSqlGenerator(Class domainType) { - return CACHE.computeIfAbsent(domainType, t -> new SqlGenerator(context, context.getRequiredPersistentEntity(t))); + return CACHE.computeIfAbsent(domainType, + t -> new SqlGenerator(context, context.getRequiredPersistentEntity(t), dialect)); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java new file mode 100644 index 0000000000..30dbf931b9 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.core.convert; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.SqlIdentifier; +import org.springframework.jdbc.core.namedparam.AbstractSqlParameterSource; + +/** + * Implementation of the {@link org.springframework.jdbc.core.namedparam.SqlParameterSource} interface based on + * {@link SqlIdentifier} instead of {@link String} for names. + * + * @author Jens Schauder + * @since 2.0 + */ +class SqlIdentifierParameterSource extends AbstractSqlParameterSource { + + private final IdentifierProcessing identifierProcessing; + private final Set identifiers = new HashSet<>(); + private final Map namesToValues = new HashMap<>(); + + SqlIdentifierParameterSource(IdentifierProcessing identifierProcessing) { + this.identifierProcessing = identifierProcessing; + } + + @Override + public boolean hasValue(String paramName) { + return namesToValues.containsKey(paramName); + } + + @Override + public Object getValue(String paramName) throws IllegalArgumentException { + return namesToValues.get(paramName); + } + + @Override + public String[] getParameterNames() { + return namesToValues.keySet().toArray(new String[0]); + } + + Set getIdentifiers() { + return Collections.unmodifiableSet(identifiers); + } + + void addValue(SqlIdentifier name, Object value) { + addValue(name, value, Integer.MIN_VALUE); + } + + void addValue(SqlIdentifier identifier, Object value, int sqlType) { + + identifiers.add(identifier); + String name = identifier.toColumnName(identifierProcessing); + namesToValues.put(name, value); + registerSqlType(name, sqlType); + } + + void addAll(SqlIdentifierParameterSource others) { + + for (SqlIdentifier identifier : others.getIdentifiers()) { + + String name = identifier.toColumnName(identifierProcessing); + addValue(identifier, others.getValue(name), others.getSqlType(name)); + } + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java index c798a6e88c..53f82dc964 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java @@ -15,17 +15,20 @@ */ package org.springframework.data.jdbc.mybatis; -import static java.util.Arrays.asList; +import static java.util.Arrays.*; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.session.SqlSession; import org.mybatis.spring.SqlSessionTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.convert.CascadingDataAccessStrategy; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy; @@ -34,9 +37,12 @@ import org.springframework.data.jdbc.core.convert.SqlGeneratorSource; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.util.Assert; @@ -54,6 +60,7 @@ * @author Oliver Gierke * @author Mark Paluch * @author Tyler Van Gorder + * @author Milan Milanov */ public class MyBatisDataAccessStrategy implements DataAccessStrategy { @@ -61,6 +68,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { private static final String VERSION_SQL_PARAMETER_NAME_OLD = "___oldOptimisticLockingVersion"; private final SqlSession sqlSession; + private final IdentifierProcessing identifierProcessing; private NamespaceStrategy namespaceStrategy = NamespaceStrategy.DEFAULT_INSTANCE; /** @@ -68,8 +76,9 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { * uses a {@link DefaultDataAccessStrategy} */ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingContext context, - JdbcConverter converter, NamedParameterJdbcOperations operations, SqlSession sqlSession) { - return createCombinedAccessStrategy(context, converter, operations, sqlSession, NamespaceStrategy.DEFAULT_INSTANCE); + JdbcConverter converter, NamedParameterJdbcOperations operations, SqlSession sqlSession, Dialect dialect) { + return createCombinedAccessStrategy(context, converter, operations, sqlSession, NamespaceStrategy.DEFAULT_INSTANCE, + dialect); } /** @@ -78,19 +87,20 @@ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingC */ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingContext context, JdbcConverter converter, NamedParameterJdbcOperations operations, SqlSession sqlSession, - NamespaceStrategy namespaceStrategy) { + NamespaceStrategy namespaceStrategy, Dialect dialect) { // the DefaultDataAccessStrategy needs a reference to the returned DataAccessStrategy. This creates a dependency // cycle. In order to create it, we need something that allows to defer closing the cycle until all the elements are // created. That is the purpose of the DelegatingAccessStrategy. DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy(); - MyBatisDataAccessStrategy myBatisDataAccessStrategy = new MyBatisDataAccessStrategy(sqlSession, context, converter); + MyBatisDataAccessStrategy myBatisDataAccessStrategy = new MyBatisDataAccessStrategy(sqlSession, + dialect.getIdentifierProcessing()); myBatisDataAccessStrategy.setNamespaceStrategy(namespaceStrategy); CascadingDataAccessStrategy cascadingDataAccessStrategy = new CascadingDataAccessStrategy( asList(myBatisDataAccessStrategy, delegatingDataAccessStrategy)); - SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(context); + SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(context, dialect); DefaultDataAccessStrategy defaultDataAccessStrategy = new DefaultDataAccessStrategy( // sqlGeneratorSource, // context, // @@ -109,13 +119,17 @@ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingC * Use a {@link SqlSessionTemplate} for {@link SqlSession} or a similar implementation tying the session to the proper * transaction. Note that the resulting {@link DataAccessStrategy} only handles MyBatis. It does not include the * functionality of the {@link DefaultDataAccessStrategy} which one normally still wants. Use - * {@link #createCombinedAccessStrategy(RelationalMappingContext, JdbcConverter, NamedParameterJdbcOperations, SqlSession, NamespaceStrategy)} + * {@link #createCombinedAccessStrategy(RelationalMappingContext, JdbcConverter, NamedParameterJdbcOperations, SqlSession, NamespaceStrategy, Dialect)} * to create such a {@link DataAccessStrategy}. * * @param sqlSession Must be non {@literal null}. + * @param identifierProcessing the {@link IdentifierProcessing} applied to {@link SqlIdentifier} instances in order to + * turn them into {@link String} */ - public MyBatisDataAccessStrategy(SqlSession sqlSession, RelationalMappingContext context, JdbcConverter converter) { + public MyBatisDataAccessStrategy(SqlSession sqlSession, IdentifierProcessing identifierProcessing) { + this.sqlSession = sqlSession; + this.identifierProcessing = identifierProcessing; } /** @@ -135,9 +149,10 @@ public void setNamespaceStrategy(NamespaceStrategy namespaceStrategy) { * @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map) */ @Override - public Object insert(T instance, Class domainType, Map additionalParameters) { + public Object insert(T instance, Class domainType, Map additionalParameters) { - MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, additionalParameters); + MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, + convertToParameterMap(additionalParameters)); sqlSession().insert(namespace(domainType) + ".insert", myBatisContext); return myBatisContext.getId(); @@ -150,7 +165,8 @@ public Object insert(T instance, Class domainType, Map ad @Override public Object insert(T instance, Class domainType, Identifier identifier) { - MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, identifier.toMap()); + MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, + convertToParameterMap(identifier.toMap())); sqlSession().insert(namespace(domainType) + ".insert", myBatisContext); return myBatisContext.getId(); @@ -174,9 +190,10 @@ public boolean update(S instance, Class domainType) { @Override public boolean updateWithVersion(S instance, Class domainType, Number previousVersion) { - - return sqlSession().update(namespace(domainType) + ".updateWithVersion", - new MyBatisContext(null, instance, domainType, Collections.singletonMap(VERSION_SQL_PARAMETER_NAME_OLD, previousVersion))) != 0; + String statement = namespace(domainType) + ".updateWithVersion"; + MyBatisContext parameter = new MyBatisContext(null, instance, domainType, + Collections.singletonMap(VERSION_SQL_PARAMETER_NAME_OLD, previousVersion)); + return sqlSession().update(statement, parameter) != 0; } /* @@ -186,8 +203,9 @@ public boolean updateWithVersion(S instance, Class domainType, Number pre @Override public void delete(Object id, Class domainType) { - sqlSession().delete(namespace(domainType) + ".delete", - new MyBatisContext(id, null, domainType, Collections.emptyMap())); + String statement = namespace(domainType) + ".delete"; + MyBatisContext parameter = new MyBatisContext(id, null, domainType, Collections.emptyMap()); + sqlSession().delete(statement, parameter); } /* @@ -197,8 +215,10 @@ public void delete(Object id, Class domainType) { @Override public void deleteWithVersion(Object id, Class domainType, Number previousVersion) { - sqlSession().delete(namespace(domainType) + ".deleteWithVersion", - new MyBatisContext(id, null, domainType, Collections.singletonMap(VERSION_SQL_PARAMETER_NAME_OLD, previousVersion))); + String statement = namespace(domainType) + ".deleteWithVersion"; + MyBatisContext parameter = new MyBatisContext(id, null, domainType, + Collections.singletonMap(VERSION_SQL_PARAMETER_NAME_OLD, previousVersion)); + sqlSession().delete(statement, parameter); } /* @@ -208,10 +228,12 @@ public void deleteWithVersion(Object id, Class domainType, Number previou @Override public void delete(Object rootId, PersistentPropertyPath propertyPath) { - sqlSession().delete( - namespace(propertyPath.getBaseProperty().getOwner().getType()) + ".delete-" + toDashPath(propertyPath), - new MyBatisContext(rootId, null, propertyPath.getRequiredLeafProperty().getTypeInformation().getType(), - Collections.emptyMap())); + Class ownerType = propertyPath.getBaseProperty().getOwner().getType(); + String statement = namespace(ownerType) + ".delete-" + toDashPath(propertyPath); + Class leafType = propertyPath.getRequiredLeafProperty().getTypeInformation().getType(); + MyBatisContext parameter = new MyBatisContext(rootId, null, leafType, Collections.emptyMap()); + + sqlSession().delete(statement, parameter); } /* @@ -221,10 +243,9 @@ public void delete(Object rootId, PersistentPropertyPath void deleteAll(Class domainType) { - sqlSession().delete( // - namespace(domainType) + ".deleteAll", // - new MyBatisContext(null, null, domainType, Collections.emptyMap()) // - ); + String statement = namespace(domainType) + ".deleteAll"; + MyBatisContext parameter = new MyBatisContext(null, null, domainType, Collections.emptyMap()); + sqlSession().delete(statement, parameter); } /* @@ -237,10 +258,9 @@ public void deleteAll(PersistentPropertyPath prope Class baseType = propertyPath.getBaseProperty().getOwner().getType(); Class leafType = propertyPath.getRequiredLeafProperty().getTypeInformation().getType(); - sqlSession().delete( // - namespace(baseType) + ".deleteAll-" + toDashPath(propertyPath), // - new MyBatisContext(null, null, leafType, Collections.emptyMap()) // - ); + String statement = namespace(baseType) + ".deleteAll-" + toDashPath(propertyPath); + MyBatisContext parameter = new MyBatisContext(null, null, leafType, Collections.emptyMap()); + sqlSession().delete(statement, parameter); } /* @@ -249,8 +269,10 @@ public void deleteAll(PersistentPropertyPath prope */ @Override public T findById(Object id, Class domainType) { - return sqlSession().selectOne(namespace(domainType) + ".findById", - new MyBatisContext(id, null, domainType, Collections.emptyMap())); + + String statement = namespace(domainType) + ".findById"; + MyBatisContext parameter = new MyBatisContext(id, null, domainType, Collections.emptyMap()); + return sqlSession().selectOne(statement, parameter); } /* @@ -259,8 +281,10 @@ public T findById(Object id, Class domainType) { */ @Override public Iterable findAll(Class domainType) { - return sqlSession().selectList(namespace(domainType) + ".findAll", - new MyBatisContext(null, null, domainType, Collections.emptyMap())); + + String statement = namespace(domainType) + ".findAll"; + MyBatisContext parameter = new MyBatisContext(null, null, domainType, Collections.emptyMap()); + return sqlSession().selectList(statement, parameter); } /* @@ -299,9 +323,9 @@ public Iterable findAllByPath(Identifier identifier, @Override public Iterable findAllByProperty(Object rootId, RelationalPersistentProperty property) { - return sqlSession().selectList( - namespace(property.getOwner().getType()) + ".findAllByProperty-" + property.getName(), - new MyBatisContext(rootId, null, property.getType(), Collections.emptyMap())); + String statement = namespace(property.getOwner().getType()) + ".findAllByProperty-" + property.getName(); + MyBatisContext parameter = new MyBatisContext(rootId, null, property.getType(), Collections.emptyMap()); + return sqlSession().selectList(statement, parameter); } /* @@ -311,8 +335,33 @@ public Iterable findAllByProperty(Object rootId, RelationalPersistentProp @Override public boolean existsById(Object id, Class domainType) { - return sqlSession().selectOne(namespace(domainType) + ".existsById", - new MyBatisContext(id, null, domainType, Collections.emptyMap())); + String statement = namespace(domainType) + ".existsById"; + MyBatisContext parameter = new MyBatisContext(id, null, domainType, Collections.emptyMap()); + return sqlSession().selectOne(statement, parameter); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class, org.springframework.data.domain.Sort) + */ + @Override + public Iterable findAll(Class domainType, Sort sort) { + Map additionalContext = new HashMap<>(); + additionalContext.put("sort", sort); + return sqlSession().selectList(namespace(domainType) + ".findAllSorted", + new MyBatisContext(null, null, domainType, additionalContext)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class, org.springframework.data.domain.Pageable) + */ + @Override + public Iterable findAll(Class domainType, Pageable pageable) { + Map additionalContext = new HashMap<>(); + additionalContext.put("pageable", pageable); + return sqlSession().selectList(namespace(domainType) + ".findAllPaged", + new MyBatisContext(null, null, domainType, additionalContext)); } /* @@ -321,8 +370,16 @@ public boolean existsById(Object id, Class domainType) { */ @Override public long count(Class domainType) { - return sqlSession().selectOne(namespace(domainType) + ".count", - new MyBatisContext(null, null, domainType, Collections.emptyMap())); + + String statement = namespace(domainType) + ".count"; + MyBatisContext parameter = new MyBatisContext(null, null, domainType, Collections.emptyMap()); + return sqlSession().selectOne(statement, parameter); + } + + private Map convertToParameterMap(Map additionalParameters) { + + return additionalParameters.entrySet().stream() // + .collect(Collectors.toMap(e -> e.getKey().toSql(identifierProcessing), Map.Entry::getValue)); } private String namespace(Class domainType) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java index dfa189be38..262f3d8955 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java @@ -34,6 +34,8 @@ import org.springframework.data.jdbc.core.convert.SqlGeneratorSource; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.conversion.RelationalConverter; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -71,8 +73,8 @@ public JdbcMappingContext jdbcMappingContext(Optional namingStra } /** - * Creates a {@link RelationalConverter} using the configured {@link #jdbcMappingContext(Optional)}. Will get - * {@link #jdbcCustomConversions()} applied. + * Creates a {@link RelationalConverter} using the configured + * {@link #jdbcMappingContext(Optional, JdbcCustomConversions)}. Will get {@link #jdbcCustomConversions()} applied. * * @see #jdbcMappingContext(Optional, JdbcCustomConversions) * @see #jdbcCustomConversions() @@ -125,7 +127,12 @@ public JdbcAggregateTemplate jdbcAggregateTemplate(ApplicationContext applicatio */ @Bean public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations operations, JdbcConverter jdbcConverter, - RelationalMappingContext context) { - return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, jdbcConverter, operations); + RelationalMappingContext context, Dialect dialect) { + return new DefaultDataAccessStrategy(new SqlGeneratorSource(context, dialect), context, jdbcConverter, operations); + } + + @Bean + Dialect dialect() { + return HsqlDbDialect.INSTANCE; } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java index cb23cc696e..f4672688bf 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java @@ -34,6 +34,8 @@ import org.springframework.data.jdbc.core.convert.SqlGeneratorSource; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.conversion.RelationalConverter; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -102,12 +104,15 @@ public JdbcCustomConversions jdbcCustomConversions() { * @param context * @param converter * @param operations + * @param dialect * @return */ @Bean public JdbcAggregateOperations jdbcAggregateOperations(ApplicationEventPublisher publisher, - RelationalMappingContext context, JdbcConverter converter, NamedParameterJdbcOperations operations) { - return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy(context, converter, operations)); + RelationalMappingContext context, JdbcConverter converter, NamedParameterJdbcOperations operations, + Dialect dialect) { + return new JdbcAggregateTemplate(publisher, context, converter, + dataAccessStrategy(context, converter, operations, dialect)); } /** @@ -117,11 +122,17 @@ public JdbcAggregateOperations jdbcAggregateOperations(ApplicationEventPublisher * @param context * @param converter * @param operations + * @param dialect * @return */ @Bean public DataAccessStrategy dataAccessStrategy(RelationalMappingContext context, JdbcConverter converter, - NamedParameterJdbcOperations operations) { - return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, operations); + NamedParameterJdbcOperations operations, Dialect dialect) { + return new DefaultDataAccessStrategy(new SqlGeneratorSource(context, dialect), context, converter, operations); + } + + @Bean + public Dialect dialect() { + return HsqlDbDialect.INSTANCE; } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/MyBatisJdbcConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/MyBatisJdbcConfiguration.java index 8406176251..c1e3a441ec 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/MyBatisJdbcConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/MyBatisJdbcConfiguration.java @@ -22,6 +22,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy; +import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -43,8 +44,8 @@ public class MyBatisJdbcConfiguration extends AbstractJdbcConfiguration { @Bean @Override public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations operations, JdbcConverter jdbcConverter, - RelationalMappingContext context) { + RelationalMappingContext context, Dialect dialect) { - return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, jdbcConverter, operations, session); + return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, jdbcConverter, operations, session, dialect); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java index 82493ce8be..10eb6cda22 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java @@ -28,6 +28,7 @@ import org.springframework.data.jdbc.repository.QueryMappingConfiguration; import org.springframework.data.jdbc.repository.RowMapperMap; import org.springframework.data.mapping.callback.EntityCallbacks; +import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; @@ -56,6 +57,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY; private NamedParameterJdbcOperations operations; private EntityCallbacks entityCallbacks; + private Dialect dialect; /** * Creates a new {@link JdbcRepositoryFactoryBean} for the given repository interface. @@ -99,6 +101,11 @@ protected void setMappingContext(RelationalMappingContext mappingContext) { this.mappingContext = mappingContext; } + @Autowired + protected void setDialect(Dialect dialect) { + this.dialect = dialect; + } + /** * @param dataAccessStrategy can be {@literal null}. */ @@ -167,7 +174,9 @@ public void afterPropertiesSet() { this.dataAccessStrategy = this.beanFactory.getBeanProvider(DataAccessStrategy.class) // .getIfAvailable(() -> { - SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(this.mappingContext); + Assert.state(this.dialect != null, "Dialect is required and must not be null!"); + + SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(this.mappingContext, this.dialect); return new DefaultDataAccessStrategy(sqlGeneratorSource, this.mappingContext, this.converter, this.operations); }); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java index fe23c84851..66093c5d1c 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java @@ -21,9 +21,13 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.JdbcAggregateOperations; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.util.Streamable; import org.springframework.transaction.annotation.Transactional; @@ -32,10 +36,11 @@ * * @author Jens Schauder * @author Oliver Gierke + * @author Milan Milanov */ @RequiredArgsConstructor @Transactional(readOnly = true) -public class SimpleJdbcRepository implements CrudRepository { +public class SimpleJdbcRepository implements CrudRepository, PagingAndSortingRepository { private final @NonNull JdbcAggregateOperations entityOperations; private final @NonNull PersistentEntity entity; @@ -144,4 +149,22 @@ public void deleteAll(Iterable entities) { public void deleteAll() { entityOperations.deleteAll(entity.getType()); } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort sort) + */ + @Override + public Iterable findAll(Sort sort) { + return entityOperations.findAll(entity.getType(), sort); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Pageable pageable) + */ + @Override + public Page findAll(Pageable pageable) { + return entityOperations.findAll(entity.getType(), pageable); + } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java index 542b0c9379..b1ad96d072 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java @@ -15,14 +15,11 @@ */ package org.springframework.data.jdbc.core; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.tuple; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.springframework.data.jdbc.core.PropertyPathTestingUtils.toPath; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.data.jdbc.core.PropertyPathTestingUtils.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import java.util.List; @@ -52,9 +49,8 @@ */ public class DefaultJdbcInterpreterUnitTests { + public static final SimpleSqlIdentifier BACK_REFERENCE = quoted("container"); static final long CONTAINER_ID = 23L; - static final String BACK_REFERENCE = "container"; - RelationalMappingContext context = new JdbcMappingContext(); JdbcConverter converter = new BasicJdbcConverter(context, (Identifier, path) -> null); DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class); @@ -156,9 +152,10 @@ public void generateCascadingIds() { assertThat(argumentCaptor.getValue().getParts()) // .extracting("name", "value", "targetType") // - .containsOnly(tuple("root_with_list", CONTAINER_ID, Long.class), // the top level id - tuple("root_with_list_key", 3, Integer.class), // midlevel key - tuple("with_list_key", 6, Integer.class) // lowlevel key + .containsOnly(tuple(quoted("root_with_list"), CONTAINER_ID, Long.class), // the top + // level id + tuple(quoted("root_with_list_key"), 3, Integer.class), // midlevel key + tuple(quoted("with_list_key"), 6, Integer.class) // lowlevel key ); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java index 1f04951aa1..bdfbfd5686 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java @@ -49,6 +49,8 @@ import org.springframework.data.annotation.Id; import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.annotation.Version; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.testing.DatabaseProfileValueSource; import org.springframework.data.jdbc.testing.HsqlDbOnly; @@ -76,6 +78,7 @@ * @author Myeonghyeon Lee * @author Tom Hombergs * @author Tyler Van Gorder + * @author Milan Milanov */ @ContextConfiguration @Transactional @@ -87,7 +90,110 @@ public class JdbcAggregateTemplateIntegrationTests { @Autowired JdbcAggregateOperations template; @Autowired NamedParameterJdbcOperations jdbcTemplate; - LegoSet legoSet = createLegoSet(); + LegoSet legoSet = createLegoSet("Star Destroyer"); + + /** + * creates an instance of {@link NoIdListChain4} with the following properties: + *
    + *
  • Each element has two children with indices 0 and 1.
  • + *
  • the xxxValue of each element is a {@literal v} followed by the indices used to navigate to the given instance. + *
  • + *
+ */ + private static NoIdListChain4 createNoIdTree() { + + NoIdListChain4 chain4 = new NoIdListChain4(); + chain4.fourValue = "v"; + + IntStream.of(0, 1).forEach(i -> { + + NoIdListChain3 c3 = new NoIdListChain3(); + c3.threeValue = chain4.fourValue + i; + chain4.chain3.add(c3); + + IntStream.of(0, 1).forEach(j -> { + + NoIdListChain2 c2 = new NoIdListChain2(); + c2.twoValue = c3.threeValue + j; + c3.chain2.add(c2); + + IntStream.of(0, 1).forEach(k -> { + + NoIdListChain1 c1 = new NoIdListChain1(); + c1.oneValue = c2.twoValue + k; + c2.chain1.add(c1); + + IntStream.of(0, 1).forEach(m -> { + + NoIdListChain0 c0 = new NoIdListChain0(); + c0.zeroValue = c1.oneValue + m; + c1.chain0.add(c0); + }); + }); + }); + }); + + return chain4; + } + + private static NoIdMapChain4 createNoIdMapTree() { + + NoIdMapChain4 chain4 = new NoIdMapChain4(); + chain4.fourValue = "v"; + + IntStream.of(0, 1).forEach(i -> { + + NoIdMapChain3 c3 = new NoIdMapChain3(); + c3.threeValue = chain4.fourValue + i; + chain4.chain3.put(asString(i), c3); + + IntStream.of(0, 1).forEach(j -> { + + NoIdMapChain2 c2 = new NoIdMapChain2(); + c2.twoValue = c3.threeValue + j; + c3.chain2.put(asString(j), c2); + + IntStream.of(0, 1).forEach(k -> { + + NoIdMapChain1 c1 = new NoIdMapChain1(); + c1.oneValue = c2.twoValue + k; + c2.chain1.put(asString(k), c1); + + IntStream.of(0, 1).forEach(it -> { + + NoIdMapChain0 c0 = new NoIdMapChain0(); + c0.zeroValue = c1.oneValue + it; + c1.chain0.put(asString(it), c0); + }); + }); + }); + }); + + return chain4; + } + + private static String asString(int i) { + return "_" + i; + } + + private static void assumeNot(String dbProfileName) { + + Assume.assumeTrue("true" + .equalsIgnoreCase(ProfileValueUtils.retrieveProfileValueSource(JdbcAggregateTemplateIntegrationTests.class) + .get("current.database.is.not." + dbProfileName))); + } + + private static LegoSet createLegoSet(String name) { + + LegoSet entity = new LegoSet(); + entity.setName(name); + + Manual manual = new Manual(); + manual.setContent("Accelerates to 99% of light speed. Destroys almost everything. See https://what-if.xkcd.com/1/"); + entity.setManual(manual); + + return entity; + } @Test // DATAJDBC-112 public void saveAndLoadAnEntityWithReferencedEntityById() { @@ -121,6 +227,39 @@ public void saveAndLoadManyEntitiesWithReferencedEntity() { .contains(tuple(legoSet.getId(), legoSet.getManual().getId(), legoSet.getManual().getContent())); } + @Test // DATAJDBC-101 + public void saveAndLoadManyEntitiesWithReferencedEntitySorted() { + template.save(createLegoSet("Lava")); + template.save(createLegoSet("Star")); + template.save(createLegoSet("Frozen")); + + Iterable reloadedLegoSets = template.findAll(LegoSet.class, Sort.by("name")); + + assertThat(reloadedLegoSets).hasSize(3).extracting("name").isEqualTo(Arrays.asList("Frozen", "Lava", "Star")); + } + + @Test // DATAJDBC-101 + public void saveAndLoadManyEntitiesWithReferencedEntityPaged() { + template.save(createLegoSet("Lava")); + template.save(createLegoSet("Star")); + template.save(createLegoSet("Frozen")); + + Iterable reloadedLegoSets = template.findAll(LegoSet.class, PageRequest.of(1, 1)); + + assertThat(reloadedLegoSets).hasSize(1).extracting("name").isEqualTo(singletonList("Star")); + } + + @Test // DATAJDBC-101 + public void saveAndLoadManyEntitiesWithReferencedEntitySortedAndPaged() { + template.save(createLegoSet("Lava")); + template.save(createLegoSet("Star")); + template.save(createLegoSet("Frozen")); + + Iterable reloadedLegoSets = template.findAll(LegoSet.class, PageRequest.of(1, 2, Sort.by("name"))); + + assertThat(reloadedLegoSets).hasSize(1).extracting("name").isEqualTo(singletonList("Star")); + } + @Test // DATAJDBC-112 public void saveAndLoadManyEntitiesByIdWithReferencedEntity() { @@ -550,50 +689,6 @@ public void shouldDeleteChainOfListsWithoutIds() { }); } - /** - * creates an instance of {@link NoIdListChain4} with the following properties: - *
    - *
  • Each element has two children with indices 0 and 1.
  • - *
  • the xxxValue of each element is a {@literal v} followed by the indices used to navigate to the given instance. - *
  • - *
- */ - private static NoIdListChain4 createNoIdTree() { - - NoIdListChain4 chain4 = new NoIdListChain4(); - chain4.fourValue = "v"; - - IntStream.of(0, 1).forEach(i -> { - - NoIdListChain3 c3 = new NoIdListChain3(); - c3.threeValue = chain4.fourValue + i; - chain4.chain3.add(c3); - - IntStream.of(0, 1).forEach(j -> { - - NoIdListChain2 c2 = new NoIdListChain2(); - c2.twoValue = c3.threeValue + j; - c3.chain2.add(c2); - - IntStream.of(0, 1).forEach(k -> { - - NoIdListChain1 c1 = new NoIdListChain1(); - c1.oneValue = c2.twoValue + k; - c2.chain1.add(c1); - - IntStream.of(0, 1).forEach(m -> { - - NoIdListChain0 c0 = new NoIdListChain0(); - c0.zeroValue = c1.oneValue + m; - c1.chain0.add(c0); - }); - }); - }); - }); - - return chain4; - } - @Test // DATAJDBC-223 public void saveAndLoadLongChainOfMapsWithoutIds() { @@ -748,57 +843,10 @@ private void saveAndUpdateAggregateWithVersion(VersionedAggre .withFailMessage("saving an aggregate with a future version should raise an exception"); } - private static NoIdMapChain4 createNoIdMapTree() { - - NoIdMapChain4 chain4 = new NoIdMapChain4(); - chain4.fourValue = "v"; - - IntStream.of(0, 1).forEach(i -> { - - NoIdMapChain3 c3 = new NoIdMapChain3(); - c3.threeValue = chain4.fourValue + i; - chain4.chain3.put(asString(i), c3); - - IntStream.of(0, 1).forEach(j -> { - - NoIdMapChain2 c2 = new NoIdMapChain2(); - c2.twoValue = c3.threeValue + j; - c3.chain2.put(asString(j), c2); - - IntStream.of(0, 1).forEach(k -> { - - NoIdMapChain1 c1 = new NoIdMapChain1(); - c1.oneValue = c2.twoValue + k; - c2.chain1.put(asString(k), c1); - - IntStream.of(0, 1).forEach(it -> { - - NoIdMapChain0 c0 = new NoIdMapChain0(); - c0.zeroValue = c1.oneValue + it; - c1.chain0.put(asString(it), c0); - }); - }); - }); - }); - - return chain4; - } - - private static String asString(int i) { - return "_" + i; - } - private Long count(String tableName) { return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM " + tableName, emptyMap(), Long.class); } - private static void assumeNot(String dbProfileName) { - - Assume.assumeTrue("true" - .equalsIgnoreCase(ProfileValueUtils.retrieveProfileValueSource(JdbcAggregateTemplateIntegrationTests.class) - .get("current.database.is.not." + dbProfileName))); - } - private static class ArrayOwner { @Id Long id; @@ -826,40 +874,28 @@ private static class SetOwner { Set digits = new HashSet<>(); } - private static LegoSet createLegoSet() { - - LegoSet entity = new LegoSet(); - entity.setName("Star Destroyer"); - - Manual manual = new Manual(); - manual.setContent("Accelerates to 99% of light speed. Destroys almost everything. See https://what-if.xkcd.com/1/"); - entity.setManual(manual); - - return entity; - } - @Data static class LegoSet { - @Column("id1") @Id private Long id; + @Column("ID1") @Id private Long id; private String name; private Manual manual; - @Column("alternative") private Manual alternativeInstructions; + @Column("ALTERNATIVE") private Manual alternativeInstructions; } @Data static class Manual { - @Column("id2") @Id private Long id; + @Column("ID2") @Id private Long id; private String content; } static class OneToOneParent { - @Column("id3") @Id private Long id; + @Column("ID3") @Id private Long id; private String content; private ChildNoId child; @@ -871,9 +907,9 @@ static class ChildNoId { static class ListParent { - @Column("id4") @Id private Long id; String name; List content = new ArrayList<>(); + @Column("ID4") @Id private Long id; } static class ElementNoId { @@ -1050,13 +1086,13 @@ static class AggregateWithPrimitiveLongVersion extends VersionedAggregate { @Version private long version; @Override - void setVersion(Number newVersion) { - this.version = (long) newVersion; + Number getVersion() { + return this.version; } @Override - Number getVersion() { - return this.version; + void setVersion(Number newVersion) { + this.version = (long) newVersion; } } @@ -1078,13 +1114,13 @@ static class AggregateWithPrimitiveIntegerVersion extends VersionedAggregate { @Version private int version; @Override - void setVersion(Number newVersion) { - this.version = (int) newVersion; + Number getVersion() { + return this.version; } @Override - Number getVersion() { - return this.version; + void setVersion(Number newVersion) { + this.version = (int) newVersion; } } @@ -1106,13 +1142,13 @@ static class AggregateWithPrimitiveShortVersion extends VersionedAggregate { @Version private short version; @Override - void setVersion(Number newVersion) { - this.version = (short) newVersion; + Number getVersion() { + return this.version; } @Override - Number getVersion() { - return this.version; + void setVersion(Number newVersion) { + this.version = (short) newVersion; } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java index d036c1ace9..f216aa75e7 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java @@ -27,11 +27,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -53,6 +53,7 @@ * * @author Christoph Strobl * @author Mark Paluch + * @author Milan Milanov */ @RunWith(MockitoJUnitRunner.class) public class JdbcAggregateTemplateUnitTests { @@ -145,6 +146,50 @@ public void callbackOnLoad() { assertThat(all).containsExactly(alfred2, neumann2); } + @Test // DATAJDBC-101 + public void callbackOnLoadSorted() { + + SampleEntity alfred1 = new SampleEntity(23L, "Alfred"); + SampleEntity alfred2 = new SampleEntity(23L, "Alfred E."); + + SampleEntity neumann1 = new SampleEntity(42L, "Neumann"); + SampleEntity neumann2 = new SampleEntity(42L, "Alfred E. Neumann"); + + when(dataAccessStrategy.findAll(SampleEntity.class, Sort.by("name"))).thenReturn(asList(alfred1, neumann1)); + + when(callbacks.callback(any(Class.class), eq(alfred1), any())).thenReturn(alfred2); + when(callbacks.callback(any(Class.class), eq(neumann1), any())).thenReturn(neumann2); + + Iterable all = template.findAll(SampleEntity.class, Sort.by("name")); + + verify(callbacks).callback(AfterLoadCallback.class, alfred1); + verify(callbacks).callback(AfterLoadCallback.class, neumann1); + + assertThat(all).containsExactly(alfred2, neumann2); + } + + @Test // DATAJDBC-101 + public void callbackOnLoadPaged() { + + SampleEntity alfred1 = new SampleEntity(23L, "Alfred"); + SampleEntity alfred2 = new SampleEntity(23L, "Alfred E."); + + SampleEntity neumann1 = new SampleEntity(42L, "Neumann"); + SampleEntity neumann2 = new SampleEntity(42L, "Alfred E. Neumann"); + + when(dataAccessStrategy.findAll(SampleEntity.class, PageRequest.of(0, 20))).thenReturn(asList(alfred1, neumann1)); + + when(callbacks.callback(any(Class.class), eq(alfred1), any())).thenReturn(alfred2); + when(callbacks.callback(any(Class.class), eq(neumann1), any())).thenReturn(neumann2); + + Iterable all = template.findAll(SampleEntity.class, PageRequest.of(0, 20)); + + verify(callbacks).callback(AfterLoadCallback.class, alfred1); + verify(callbacks).callback(AfterLoadCallback.class, neumann1); + + assertThat(all).containsExactly(alfred2, neumann2); + } + @Data @AllArgsConstructor private static class SampleEntity { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java index 1c60da9862..319bd1fe27 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java @@ -15,9 +15,11 @@ */ package org.springframework.data.jdbc.core; +import static org.assertj.core.api.SoftAssertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; + import java.util.List; -import org.jetbrains.annotations.NotNull; import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; @@ -28,8 +30,6 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import static org.assertj.core.api.SoftAssertions.*; - /** * @author Jens Schauder */ @@ -99,13 +99,13 @@ public void getTableName() { assertSoftly(softly -> { - softly.assertThat(extPath(entity).getTableName()).isEqualTo("dummy_entity"); - softly.assertThat(extPath("second").getTableName()).isEqualTo("second"); - softly.assertThat(extPath("second.third2").getTableName()).isEqualTo("second"); - softly.assertThat(extPath("second.third2.value").getTableName()).isEqualTo("second"); - softly.assertThat(extPath("secondList.third2").getTableName()).isEqualTo("second"); - softly.assertThat(extPath("secondList.third2.value").getTableName()).isEqualTo("second"); - softly.assertThat(extPath("secondList").getTableName()).isEqualTo("second"); + softly.assertThat(extPath(entity).getTableName()).isEqualTo(quoted("dummy_entity")); + softly.assertThat(extPath("second").getTableName()).isEqualTo(quoted("second")); + softly.assertThat(extPath("second.third2").getTableName()).isEqualTo(quoted("second")); + softly.assertThat(extPath("second.third2.value").getTableName()).isEqualTo(quoted("second")); + softly.assertThat(extPath("secondList.third2").getTableName()).isEqualTo(quoted("second")); + softly.assertThat(extPath("secondList.third2.value").getTableName()).isEqualTo(quoted("second")); + softly.assertThat(extPath("secondList").getTableName()).isEqualTo(quoted("second")); }); } @@ -115,17 +115,17 @@ public void getTableAlias() { assertSoftly(softly -> { softly.assertThat(extPath(entity).getTableAlias()).isEqualTo(null); - softly.assertThat(extPath("second").getTableAlias()).isEqualTo("second"); - softly.assertThat(extPath("second.third2").getTableAlias()).isEqualTo("second"); - softly.assertThat(extPath("second.third2.value").getTableAlias()).isEqualTo("second"); - softly.assertThat(extPath("second.third").getTableAlias()).isEqualTo("second_third"); - softly.assertThat(extPath("second.third.value").getTableAlias()).isEqualTo("second_third"); - softly.assertThat(extPath("secondList.third2").getTableAlias()).isEqualTo("secondList"); - softly.assertThat(extPath("secondList.third2.value").getTableAlias()).isEqualTo("secondList"); - softly.assertThat(extPath("secondList.third").getTableAlias()).isEqualTo("secondList_third"); - softly.assertThat(extPath("secondList.third.value").getTableAlias()).isEqualTo("secondList_third"); - softly.assertThat(extPath("secondList").getTableAlias()).isEqualTo("secondList"); - softly.assertThat(extPath("second2.third").getTableAlias()).isEqualTo("secthird"); + softly.assertThat(extPath("second").getTableAlias()).isEqualTo(quoted("second")); + softly.assertThat(extPath("second.third2").getTableAlias()).isEqualTo(quoted("second")); + softly.assertThat(extPath("second.third2.value").getTableAlias()).isEqualTo(quoted("second")); + softly.assertThat(extPath("second.third").getTableAlias()).isEqualTo(quoted("second_third")); + softly.assertThat(extPath("second.third.value").getTableAlias()).isEqualTo(quoted("second_third")); + softly.assertThat(extPath("secondList.third2").getTableAlias()).isEqualTo(quoted("secondList")); + softly.assertThat(extPath("secondList.third2.value").getTableAlias()).isEqualTo(quoted("secondList")); + softly.assertThat(extPath("secondList.third").getTableAlias()).isEqualTo(quoted("secondList_third")); + softly.assertThat(extPath("secondList.third.value").getTableAlias()).isEqualTo(quoted("secondList_third")); + softly.assertThat(extPath("secondList").getTableAlias()).isEqualTo(quoted("secondList")); + softly.assertThat(extPath("second2.third").getTableAlias()).isEqualTo(quoted("secthird")); }); } @@ -134,12 +134,12 @@ public void getColumnName() { assertSoftly(softly -> { - softly.assertThat(extPath("second.third2.value").getColumnName()).isEqualTo("thrdvalue"); - softly.assertThat(extPath("second.third.value").getColumnName()).isEqualTo("value"); - softly.assertThat(extPath("secondList.third2.value").getColumnName()).isEqualTo("thrdvalue"); - softly.assertThat(extPath("secondList.third.value").getColumnName()).isEqualTo("value"); - softly.assertThat(extPath("second2.third2.value").getColumnName()).isEqualTo("secthrdvalue"); - softly.assertThat(extPath("second2.third.value").getColumnName()).isEqualTo("value"); + softly.assertThat(extPath("second.third2.value").getColumnName()).isEqualTo(quoted("thrdvalue")); + softly.assertThat(extPath("second.third.value").getColumnName()).isEqualTo(quoted("value")); + softly.assertThat(extPath("secondList.third2.value").getColumnName()).isEqualTo(quoted("thrdvalue")); + softly.assertThat(extPath("secondList.third.value").getColumnName()).isEqualTo(quoted("value")); + softly.assertThat(extPath("second2.third2.value").getColumnName()).isEqualTo(quoted("secthrdvalue")); + softly.assertThat(extPath("second2.third.value").getColumnName()).isEqualTo(quoted("value")); }); } @@ -164,15 +164,15 @@ public void reverseColumnName() { assertSoftly(softly -> { - softly.assertThat(extPath("second.third2").getReverseColumnName()).isEqualTo("dummy_entity"); - softly.assertThat(extPath("second.third").getReverseColumnName()).isEqualTo("dummy_entity"); - softly.assertThat(extPath("secondList.third2").getReverseColumnName()).isEqualTo("dummy_entity"); - softly.assertThat(extPath("secondList.third").getReverseColumnName()).isEqualTo("dummy_entity"); - softly.assertThat(extPath("second2.third2").getReverseColumnName()).isEqualTo("dummy_entity"); - softly.assertThat(extPath("second2.third").getReverseColumnName()).isEqualTo("dummy_entity"); - softly.assertThat(extPath("withId.second.third2.value").getReverseColumnName()).isEqualTo("with_id"); - softly.assertThat(extPath("withId.second.third").getReverseColumnName()).isEqualTo("with_id"); - softly.assertThat(extPath("withId.second2.third").getReverseColumnName()).isEqualTo("with_id"); + softly.assertThat(extPath("second.third2").getReverseColumnName()).isEqualTo(quoted("dummy_entity")); + softly.assertThat(extPath("second.third").getReverseColumnName()).isEqualTo(quoted("dummy_entity")); + softly.assertThat(extPath("secondList.third2").getReverseColumnName()).isEqualTo(quoted("dummy_entity")); + softly.assertThat(extPath("secondList.third").getReverseColumnName()).isEqualTo(quoted("dummy_entity")); + softly.assertThat(extPath("second2.third2").getReverseColumnName()).isEqualTo(quoted("dummy_entity")); + softly.assertThat(extPath("second2.third").getReverseColumnName()).isEqualTo(quoted("dummy_entity")); + softly.assertThat(extPath("withId.second.third2.value").getReverseColumnName()).isEqualTo(quoted("with_id")); + softly.assertThat(extPath("withId.second.third").getReverseColumnName()).isEqualTo(quoted("with_id")); + softly.assertThat(extPath("withId.second2.third").getReverseColumnName()).isEqualTo(quoted("with_id")); }); } @@ -200,12 +200,10 @@ public void extendBy() { }); } - @NotNull private PersistentPropertyPathExtension extPath(RelationalPersistentEntity entity) { return new PersistentPropertyPathExtension(context, entity); } - @NotNull private PersistentPropertyPathExtension extPath(String path) { return new PersistentPropertyPathExtension(context, createSimplePath(path)); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java index f9d876dd45..53c568cf58 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; @@ -33,7 +34,10 @@ import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; @@ -54,7 +58,7 @@ public class DefaultDataAccessStrategyUnitTests { JdbcOperations jdbcOperations = mock(JdbcOperations.class); RelationalMappingContext context = new JdbcMappingContext(); - HashMap additionalParameters = new HashMap<>(); + HashMap additionalParameters = new HashMap<>(); ArgumentCaptor paramSourceCaptor = ArgumentCaptor.forClass(SqlParameterSource.class); JdbcConverter converter; @@ -62,12 +66,14 @@ public class DefaultDataAccessStrategyUnitTests { @Before public void before() { + DelegatingDataAccessStrategy relationResolver = new DelegatingDataAccessStrategy(); + Dialect dialect = HsqlDbDialect.INSTANCE; converter = new BasicJdbcConverter(context, relationResolver, new JdbcCustomConversions(), new DefaultJdbcTypeFactory(jdbcOperations)); accessStrategy = new DefaultDataAccessStrategy( // - new SqlGeneratorSource(context), // + new SqlGeneratorSource(context, dialect), // context, // converter, // namedJdbcOperations); @@ -78,12 +84,12 @@ public void before() { @Test // DATAJDBC-146 public void additionalParameterForIdDoesNotLeadToDuplicateParameters() { - additionalParameters.put("id", ID_FROM_ADDITIONAL_VALUES); + additionalParameters.put(SqlIdentifier.quoted("ID"), ID_FROM_ADDITIONAL_VALUES); accessStrategy.insert(new DummyEntity(ORIGINAL_ID), DummyEntity.class, additionalParameters); - verify(namedJdbcOperations).update(eq("INSERT INTO dummy_entity (id) VALUES (:id)"), paramSourceCaptor.capture(), - any(KeyHolder.class)); + verify(namedJdbcOperations).update(eq("INSERT INTO \"DUMMY_ENTITY\" (\"ID\") VALUES (:ID)"), + paramSourceCaptor.capture(), any(KeyHolder.class)); } @Test // DATAJDBC-146 @@ -91,16 +97,16 @@ public void additionalParametersGetAddedToStatement() { ArgumentCaptor sqlCaptor = ArgumentCaptor.forClass(String.class); - additionalParameters.put("reference", ID_FROM_ADDITIONAL_VALUES); + additionalParameters.put(unquoted("reference"), ID_FROM_ADDITIONAL_VALUES); accessStrategy.insert(new DummyEntity(ORIGINAL_ID), DummyEntity.class, additionalParameters); verify(namedJdbcOperations).update(sqlCaptor.capture(), paramSourceCaptor.capture(), any(KeyHolder.class)); assertThat(sqlCaptor.getValue()) // - .containsSequence("INSERT INTO dummy_entity (", "id", ") VALUES (", ":id", ")") // - .containsSequence("INSERT INTO dummy_entity (", "reference", ") VALUES (", ":reference", ")"); - assertThat(paramSourceCaptor.getValue().getValue("id")).isEqualTo(ORIGINAL_ID); + .containsSequence("INSERT INTO \"DUMMY_ENTITY\" (", "\"ID\"", ") VALUES (", ":ID", ")") // + .containsSequence("INSERT INTO \"DUMMY_ENTITY\" (", "reference", ") VALUES (", ":reference", ")"); + assertThat(paramSourceCaptor.getValue().getValue("ID")).isEqualTo(ORIGINAL_ID); } @Test // DATAJDBC-235 @@ -113,7 +119,7 @@ public void considersConfiguredWriteConverter() { new DefaultJdbcTypeFactory(jdbcOperations)); DefaultDataAccessStrategy accessStrategy = new DefaultDataAccessStrategy( // - new SqlGeneratorSource(context), // + new SqlGeneratorSource(context, HsqlDbDialect.INSTANCE), // context, // converter, // namedJdbcOperations); @@ -128,8 +134,8 @@ public void considersConfiguredWriteConverter() { verify(namedJdbcOperations).update(sqlCaptor.capture(), paramSourceCaptor.capture(), any(KeyHolder.class)); - assertThat(paramSourceCaptor.getValue().getValue("id")).isEqualTo(ORIGINAL_ID); - assertThat(paramSourceCaptor.getValue().getValue("flag")).isEqualTo("T"); + assertThat(paramSourceCaptor.getValue().getValue("ID")).isEqualTo(ORIGINAL_ID); + assertThat(paramSourceCaptor.getValue().getValue("FLAG")).isEqualTo("T"); } @RequiredArgsConstructor diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java index fd5208c38d..f2ec4975e8 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/EntityRowMapperUnitTests.java @@ -60,6 +60,7 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.relational.domain.SqlIdentifier.SimpleSqlIdentifier; import org.springframework.data.repository.query.Param; import org.springframework.util.Assert; @@ -80,15 +81,15 @@ public class EntityRowMapperUnitTests { public static final long ID_FOR_ENTITY_NOT_REFERENCING_MAP = 23L; public static final NamingStrategy X_APPENDING_NAMINGSTRATEGY = new NamingStrategy() { @Override - public String getColumnName(RelationalPersistentProperty property) { - return NamingStrategy.super.getColumnName(property) + "x"; + public SimpleSqlIdentifier getColumnName(RelationalPersistentProperty property) { + return NamingStrategy.super.getColumnName(property).suffix("x"); } }; @Test // DATAJDBC-113 public void simpleEntitiesGetProperlyExtracted() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "name"), // + ResultSet rs = mockResultSet(asList("ID", "NAME"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha"); rs.next(); @@ -103,7 +104,7 @@ public void simpleEntitiesGetProperlyExtracted() throws SQLException { @Test // DATAJDBC-181 public void namingStrategyGetsHonored() throws SQLException { - ResultSet rs = mockResultSet(asList("idx", "namex"), // + ResultSet rs = mockResultSet(asList("IDX", "NAMEX"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha"); rs.next(); @@ -118,7 +119,7 @@ public void namingStrategyGetsHonored() throws SQLException { @Test // DATAJDBC-181 public void namingStrategyGetsHonoredForConstructor() throws SQLException { - ResultSet rs = mockResultSet(asList("idx", "namex"), // + ResultSet rs = mockResultSet(asList("IDX", "NAMEX"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha"); rs.next(); @@ -133,7 +134,7 @@ public void namingStrategyGetsHonoredForConstructor() throws SQLException { @Test // DATAJDBC-427 public void simpleWithReferenceGetProperlyExtracted() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "name", "trivial_id"), // + ResultSet rs = mockResultSet(asList("ID", "NAME", "TRIVIAL_ID"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 100L); rs.next(); @@ -148,7 +149,7 @@ public void simpleWithReferenceGetProperlyExtracted() throws SQLException { @Test // DATAJDBC-113 public void simpleOneToOneGetsProperlyExtracted() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "name", "child_id", "child_name"), // + ResultSet rs = mockResultSet(asList("ID", "NAME", "CHILD_ID", "CHILD_NAME"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24L, "beta"); rs.next(); @@ -163,7 +164,7 @@ public void simpleOneToOneGetsProperlyExtracted() throws SQLException { @Test // DATAJDBC-286 public void immutableOneToOneGetsProperlyExtracted() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "name", "child_id", "child_name"), // + ResultSet rs = mockResultSet(asList("ID", "NAME", "CHILD_ID", "CHILD_NAME"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24L, "beta"); rs.next(); @@ -178,7 +179,7 @@ public void immutableOneToOneGetsProperlyExtracted() throws SQLException { @Test // DATAJDBC-427 public void immutableWithReferenceGetsProperlyExtracted() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "name", "trivial_id"), // + ResultSet rs = mockResultSet(asList("ID", "NAME", "TRIVIAL_ID"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 100L); rs.next(); @@ -194,7 +195,7 @@ public void immutableWithReferenceGetsProperlyExtracted() throws SQLException { @Test // DATAJDBC-111 public void simpleEmbeddedGetsProperlyExtracted() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "name", "prefix_id", "prefix_name"), // + ResultSet rs = mockResultSet(asList("ID", "NAME", "PREFIX_ID", "PREFIX_NAME"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24L, "beta"); rs.next(); @@ -209,7 +210,7 @@ public void simpleEmbeddedGetsProperlyExtracted() throws SQLException { @Test // DATAJDBC-113 public void collectionReferenceGetsLoadedWithAdditionalSelect() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "name"), // + ResultSet rs = mockResultSet(asList("ID", "NAME"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha"); rs.next(); @@ -224,7 +225,7 @@ public void collectionReferenceGetsLoadedWithAdditionalSelect() throws SQLExcept @Test // DATAJDBC-131 public void mapReferenceGetsLoadedWithAdditionalSelect() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "name"), // + ResultSet rs = mockResultSet(asList("ID", "NAME"), // ID_FOR_ENTITY_REFERENCING_MAP, "alpha"); rs.next(); @@ -239,7 +240,7 @@ public void mapReferenceGetsLoadedWithAdditionalSelect() throws SQLException { @Test // DATAJDBC-130 public void listReferenceGetsLoadedWithAdditionalSelect() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "name"), // + ResultSet rs = mockResultSet(asList("ID", "NAME"), // ID_FOR_ENTITY_REFERENCING_LIST, "alpha"); rs.next(); @@ -254,7 +255,7 @@ public void listReferenceGetsLoadedWithAdditionalSelect() throws SQLException { @Test // DATAJDBC-252 public void doesNotTryToSetPropertiesThatAreSetViaConstructor() throws SQLException { - ResultSet rs = mockResultSet(singletonList("value"), // + ResultSet rs = mockResultSet(singletonList("VALUE"), // "value-from-resultSet"); rs.next(); @@ -267,7 +268,7 @@ public void doesNotTryToSetPropertiesThatAreSetViaConstructor() throws SQLExcept @Test // DATAJDBC-252 public void handlesMixedProperties() throws SQLException { - ResultSet rs = mockResultSet(asList("one", "two", "three"), // + ResultSet rs = mockResultSet(asList("ONE", "TWO", "THREE"), // "111", "222", "333"); rs.next(); @@ -281,7 +282,7 @@ public void handlesMixedProperties() throws SQLException { @Test // DATAJDBC-273 public void handlesNonSimplePropertyInConstructor() throws SQLException { - ResultSet rs = mockResultSet(singletonList("id"), // + ResultSet rs = mockResultSet(singletonList("ID"), // ID_FOR_ENTITY_REFERENCING_LIST); rs.next(); @@ -297,22 +298,22 @@ public void chainedEntitiesWithoutId() throws SQLException { Fixture fixture = this. buildFixture() // // Id of the aggregate root and backreference to it from // the various aggregate members. - .value(4L).inColumns("four", // - "chain3_no_id_chain4", // - "chain3_chain2_no_id_chain4", // - "chain3_chain2_chain1_no_id_chain4", // - "chain3_chain2_chain1_chain0_no_id_chain4") // + .value(4L).inColumns("FOUR", // + "CHAIN3_NO_ID_CHAIN4", // + "CHAIN3_CHAIN2_NO_ID_CHAIN4", // + "CHAIN3_CHAIN2_CHAIN1_NO_ID_CHAIN4", // + "CHAIN3_CHAIN2_CHAIN1_CHAIN0_NO_ID_CHAIN4") // .endUpIn(e -> e.four) // values for the different entities - .value("four_value").inColumns("four_value").endUpIn(e -> e.fourValue) // + .value("four_value").inColumns("FOUR_VALUE").endUpIn(e -> e.fourValue) // - .value("three_value").inColumns("chain3_three_value").endUpIn(e -> e.chain3.threeValue) // + .value("three_value").inColumns("CHAIN3_THREE_VALUE").endUpIn(e -> e.chain3.threeValue) // - .value("two_value").inColumns("chain3_chain2_two_value").endUpIn(e -> e.chain3.chain2.twoValue) // + .value("two_value").inColumns("CHAIN3_CHAIN2_TWO_VALUE").endUpIn(e -> e.chain3.chain2.twoValue) // - .value("one_value").inColumns("chain3_chain2_chain1_one_value").endUpIn(e -> e.chain3.chain2.chain1.oneValue) // + .value("one_value").inColumns("CHAIN3_CHAIN2_CHAIN1_ONE_VALUE").endUpIn(e -> e.chain3.chain2.chain1.oneValue) // - .value("zero_value").inColumns("chain3_chain2_chain1_chain0_zero_value") + .value("zero_value").inColumns("CHAIN3_CHAIN2_CHAIN1_CHAIN0_ZERO_VALUE") .endUpIn(e -> e.chain3.chain2.chain1.chain0.zeroValue) // .build(); // @formatter:on @@ -329,7 +330,7 @@ public void chainedEntitiesWithoutId() throws SQLException { @Test // DATAJDBC-370 public void simpleNullableImmutableEmbeddedGetsProperlyExtracted() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "value"), // + ResultSet rs = mockResultSet(asList("ID", "VALUE"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "ru'Ha'"); rs.next(); @@ -345,7 +346,7 @@ public void simpleNullableImmutableEmbeddedGetsProperlyExtracted() throws SQLExc @Test // DATAJDBC-374 public void simpleEmptyImmutableEmbeddedGetsProperlyExtracted() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "value"), // + ResultSet rs = mockResultSet(asList("ID", "VALUE"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); rs.next(); @@ -361,7 +362,7 @@ public void simpleEmptyImmutableEmbeddedGetsProperlyExtracted() throws SQLExcept @SneakyThrows public void simplePrimitiveImmutableEmbeddedGetsProperlyExtracted() { - ResultSet rs = mockResultSet(asList("id", "value"), // + ResultSet rs = mockResultSet(asList("ID", "VALUE"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, 24); rs.next(); @@ -377,7 +378,7 @@ public void simplePrimitiveImmutableEmbeddedGetsProperlyExtracted() { @Test // DATAJDBC-370 public void simpleImmutableEmbeddedShouldBeNullIfAllOfTheEmbeddableAreNull() throws SQLException { - ResultSet rs = mockResultSet(asList("id", "value"), // + ResultSet rs = mockResultSet(asList("ID", "VALUE"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); rs.next(); @@ -394,7 +395,7 @@ public void simpleImmutableEmbeddedShouldBeNullIfAllOfTheEmbeddableAreNull() thr @SneakyThrows public void embeddedShouldBeNullWhenFieldsAreNull() { - ResultSet rs = mockResultSet(asList("id", "name", "prefix_id", "prefix_name"), // + ResultSet rs = mockResultSet(asList("ID", "NAME", "PREFIX_ID", "PREFIX_NAME"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", null, null); rs.next(); @@ -410,7 +411,7 @@ public void embeddedShouldBeNullWhenFieldsAreNull() { @SneakyThrows public void embeddedShouldNotBeNullWhenAtLeastOneFieldIsNotNull() { - ResultSet rs = mockResultSet(asList("id", "name", "prefix_id", "prefix_name"), // + ResultSet rs = mockResultSet(asList("ID", "NAME", "PREFIX_ID", "PREFIX_NAME"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "alpha", 24, null); rs.next(); @@ -426,7 +427,7 @@ public void embeddedShouldNotBeNullWhenAtLeastOneFieldIsNotNull() { @SneakyThrows public void primitiveEmbeddedShouldBeNullWhenNoValuePresent() { - ResultSet rs = mockResultSet(asList("id", "value"), // + ResultSet rs = mockResultSet(asList("ID", "VALUE"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, null); rs.next(); @@ -443,7 +444,7 @@ public void primitiveEmbeddedShouldBeNullWhenNoValuePresent() { @SneakyThrows public void deepNestedEmbeddable() { - ResultSet rs = mockResultSet(asList("id", "level0", "level1_value", "level1_level2_value"), // + ResultSet rs = mockResultSet(asList("ID", "LEVEL0", "LEVEL1_VALUE", "LEVEL1_LEVEL2_VALUE"), // ID_FOR_ENTITY_NOT_REFERENCING_MAP, "0", "1", "2"); rs.next(); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java index df2d1b9766..1fa60c078b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.*; import static org.springframework.data.jdbc.core.PropertyPathTestingUtils.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import java.util.List; import java.util.Map; @@ -46,7 +47,7 @@ public void parametersWithPropertyKeysUseTheParentPropertyJdbcType() { assertThat(identifier.getParts()) // .extracting("name", "value", "targetType") // .containsExactly( // - tuple("dummy_entity", "eins", UUID.class) // + tuple(quoted("dummy_entity"), "eins", UUID.class) // ); } @@ -63,8 +64,8 @@ public void qualifiersForMaps() { assertThat(identifier.getParts()) // .extracting("name", "value", "targetType") // .containsExactlyInAnyOrder( // - tuple("dummy_entity", "parent-eins", UUID.class), // - tuple("dummy_entity_key", "map-key-eins", String.class) // + tuple(quoted("dummy_entity"), "parent-eins", UUID.class), // + tuple(quoted("dummy_entity_key"), "map-key-eins", String.class) // ); } @@ -81,8 +82,8 @@ public void qualifiersForLists() { assertThat(identifier.getParts()) // .extracting("name", "value", "targetType") // .containsExactlyInAnyOrder( // - tuple("dummy_entity", "parent-eins", UUID.class), // - tuple("dummy_entity_key", "list-index-eins", Integer.class) // + tuple(quoted("dummy_entity"), "parent-eins", UUID.class), // + tuple(quoted("dummy_entity_key"), "list-index-eins", Integer.class) // ); } @@ -96,7 +97,7 @@ public void backreferenceAcrossEmbeddable() { assertThat(identifier.getParts()) // .extracting("name", "value", "targetType") // .containsExactly( // - tuple("dummy_entity", "parent-eins", UUID.class) // + tuple(quoted("dummy_entity"), "parent-eins", UUID.class) // ); } @@ -110,7 +111,7 @@ public void backreferenceAcrossNoId() { assertThat(identifier.getParts()) // .extracting("name", "value", "targetType") // .containsExactly( // - tuple("dummy_entity", "parent-eins", UUID.class) // + tuple(quoted("dummy_entity"), "parent-eins", UUID.class) // ); } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java index dbe5cd09cb..1422c5d1b8 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.core.convert; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -25,14 +26,18 @@ import org.assertj.core.api.SoftAssertions; import org.junit.Test; import org.springframework.data.annotation.Id; -import org.springframework.data.jdbc.core.convert.SqlGenerator; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils; +import org.springframework.data.jdbc.testing.NonQuotingDialect; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.NamingStrategy; 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.relational.domain.IdentifierProcessing.DefaultIdentifierProcessing; +import org.springframework.data.relational.domain.IdentifierProcessing.LetterCasing; +import org.springframework.data.relational.domain.IdentifierProcessing.Quoting; +import org.springframework.data.relational.domain.SqlIdentifier; /** * Unit tests to verify a contextual {@link NamingStrategy} implementation that customizes using a user-centric @@ -52,8 +57,8 @@ public class SqlGeneratorContextBasedNamingStrategyUnitTests { private final NamingStrategy contextualNamingStrategy = new NamingStrategy() { @Override - public String getSchema() { - return userHandler.get(); + public SqlIdentifier getSchema() { + return unquoted(userHandler.get()); } }; @@ -87,8 +92,11 @@ public void cascadingDeleteFirstLevel() { String sql = sqlGenerator.createDeleteByPath(getPath("ref")); - assertThat(sql).isEqualTo( - "DELETE FROM " + user + ".referenced_entity WHERE " + user + ".referenced_entity.dummy_entity = :rootId"); + assertThat(sql).isEqualTo( // + "DELETE FROM " // + + user + ".referenced_entity WHERE " // + + user + ".referenced_entity.dummy_entity = :rootId" // + ); }); } @@ -213,7 +221,7 @@ private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { RelationalMappingContext context = new JdbcMappingContext(namingStrategy); RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); - return new SqlGenerator(context, persistentEntity); + return new SqlGenerator(context, persistentEntity, NonQuotingDialect.INSTANCE); } @SuppressWarnings("unused") diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java index fb5481ab40..75b86ee756 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java @@ -25,6 +25,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.PropertyPathTestingUtils; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; +import org.springframework.data.jdbc.testing.NonQuotingDialect; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Embedded; import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; @@ -32,6 +33,9 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.sql.Aliased; +import org.springframework.data.relational.domain.IdentifierProcessing.DefaultIdentifierProcessing; +import org.springframework.data.relational.domain.IdentifierProcessing.LetterCasing; +import org.springframework.data.relational.domain.IdentifierProcessing.Quoting; /** * Unit tests for the {@link SqlGenerator} in a context of the {@link Embedded} annotation. @@ -50,81 +54,120 @@ public void setUp() { SqlGenerator createSqlGenerator(Class type) { RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(type); - return new SqlGenerator(context, persistentEntity); + return new SqlGenerator(context, persistentEntity, NonQuotingDialect.INSTANCE); } @Test // DATAJDBC-111 public void findOne() { final String sql = sqlGenerator.getFindOne(); - SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(sql).startsWith("SELECT").contains("dummy_entity.id1 AS id1") - .contains("dummy_entity.test AS test").contains("dummy_entity.attr1 AS attr1") - .contains("dummy_entity.attr2 AS attr2").contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") - .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2").contains("dummy_entity.prefix_test AS prefix_test") - .contains("dummy_entity.prefix_attr1 AS prefix_attr1").contains("dummy_entity.prefix_attr2 AS prefix_attr2") - .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") - .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2").contains("WHERE dummy_entity.id1 = :id") - .doesNotContain("JOIN").doesNotContain("embeddable"); - softAssertions.assertAll(); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(sql).startsWith("SELECT") // + .contains("dummy_entity.id1 AS id1") // + .contains("dummy_entity.test AS test") // + .contains("dummy_entity.attr1 AS attr1") // + .contains("dummy_entity.attr2 AS attr2") // + .contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") // + .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2") // + .contains("dummy_entity.prefix_test AS prefix_test") // + .contains("dummy_entity.prefix_attr1 AS prefix_attr1") // + .contains("dummy_entity.prefix_attr2 AS prefix_attr2") // + .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") // + .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2") // + .contains("WHERE dummy_entity.id1 = :id") // + .doesNotContain("JOIN").doesNotContain("embeddable"); // + }); } @Test // DATAJDBC-111 public void findAll() { final String sql = sqlGenerator.getFindAll(); - SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(sql).startsWith("SELECT").contains("dummy_entity.id1 AS id1") - .contains("dummy_entity.test AS test").contains("dummy_entity.attr1 AS attr1") - .contains("dummy_entity.attr2 AS attr2").contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") - .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2").contains("dummy_entity.prefix_test AS prefix_test") - .contains("dummy_entity.prefix_attr1 AS prefix_attr1").contains("dummy_entity.prefix_attr2 AS prefix_attr2") - .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") - .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2").doesNotContain("JOIN") - .doesNotContain("embeddable"); - softAssertions.assertAll(); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(sql).startsWith("SELECT") // + .contains("dummy_entity.id1 AS id1") // + .contains("dummy_entity.test AS test") // + .contains("dummy_entity.attr1 AS attr1") // + .contains("dummy_entity.attr2 AS attr2") // + .contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") // + .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2") // + .contains("dummy_entity.prefix_test AS prefix_test") // + .contains("dummy_entity.prefix_attr1 AS prefix_attr1") // + .contains("dummy_entity.prefix_attr2 AS prefix_attr2") // + .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") // + .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2") // + .doesNotContain("JOIN") // + .doesNotContain("embeddable"); + }); } @Test // DATAJDBC-111 public void findAllInList() { final String sql = sqlGenerator.getFindAllInList(); - SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(sql).startsWith("SELECT").contains("dummy_entity.id1 AS id1") - .contains("dummy_entity.test AS test").contains("dummy_entity.attr1 AS attr1") - .contains("dummy_entity.attr2 AS attr2").contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") - .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2").contains("dummy_entity.prefix_test AS prefix_test") - .contains("dummy_entity.prefix_attr1 AS prefix_attr1").contains("dummy_entity.prefix_attr2 AS prefix_attr2") - .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") - .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2") - .contains("WHERE dummy_entity.id1 IN (:ids)").doesNotContain("JOIN").doesNotContain("embeddable"); - softAssertions.assertAll(); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(sql).startsWith("SELECT") // + .contains("dummy_entity.id1 AS id1") // + .contains("dummy_entity.test AS test") // + .contains("dummy_entity.attr1 AS attr1") // + .contains("dummy_entity.attr2 AS attr2").contains("dummy_entity.prefix2_attr1 AS prefix2_attr1") // + .contains("dummy_entity.prefix2_attr2 AS prefix2_attr2") // + .contains("dummy_entity.prefix_test AS prefix_test") // + .contains("dummy_entity.prefix_attr1 AS prefix_attr1") // + .contains("dummy_entity.prefix_attr2 AS prefix_attr2") // + .contains("dummy_entity.prefix_prefix2_attr1 AS prefix_prefix2_attr1") // + .contains("dummy_entity.prefix_prefix2_attr2 AS prefix_prefix2_attr2") // + .contains("WHERE dummy_entity.id1 IN (:ids)") // + .doesNotContain("JOIN") // + .doesNotContain("embeddable"); + }); } @Test // DATAJDBC-111 public void insert() { final String sql = sqlGenerator.getInsert(emptySet()); - SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(sql).startsWith("INSERT INTO").contains("dummy_entity").contains(":test") - .contains(":attr1").contains(":attr2").contains(":prefix2_attr1").contains(":prefix2_attr2") - .contains(":prefix_test").contains(":prefix_attr1").contains(":prefix_attr2").contains(":prefix_prefix2_attr1") - .contains(":prefix_prefix2_attr2"); - softAssertions.assertAll(); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(sql) // + .startsWith("INSERT INTO") // + .contains("dummy_entity") // + .contains(":test") // + .contains(":attr1") // + .contains(":attr2") // + .contains(":prefix2_attr1") // + .contains(":prefix2_attr2") // + .contains(":prefix_test") // + .contains(":prefix_attr1") // + .contains(":prefix_attr2") // + .contains(":prefix_prefix2_attr1") // + .contains(":prefix_prefix2_attr2"); + }); } @Test // DATAJDBC-111 public void update() { final String sql = sqlGenerator.getUpdate(); - SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(sql).startsWith("UPDATE").contains("dummy_entity").contains("test = :test") - .contains("attr1 = :attr1").contains("attr2 = :attr2").contains("prefix2_attr1 = :prefix2_attr1") - .contains("prefix2_attr2 = :prefix2_attr2").contains("prefix_test = :prefix_test") - .contains("prefix_attr1 = :prefix_attr1").contains("prefix_attr2 = :prefix_attr2") - .contains("prefix_prefix2_attr1 = :prefix_prefix2_attr1") - .contains("prefix_prefix2_attr2 = :prefix_prefix2_attr2"); - softAssertions.assertAll(); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(sql) // + .startsWith("UPDATE") // + .contains("dummy_entity") // + .contains("test = :test") // + .contains("attr1 = :attr1") // + .contains("attr2 = :attr2") // + .contains("prefix2_attr1 = :prefix2_attr1") // + .contains("prefix2_attr2 = :prefix2_attr2") // + .contains("prefix_test = :prefix_test") // + .contains("prefix_attr1 = :prefix_attr1") // + .contains("prefix_attr2 = :prefix_attr2") // + .contains("prefix_prefix2_attr1 = :prefix_prefix2_attr1") // + .contains("prefix_prefix2_attr2 = :prefix_prefix2_attr2"); + }); } @Test // DATAJDBC-340 diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java index 6fdb9bdef7..ce17e08269 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java @@ -16,17 +16,22 @@ package org.springframework.data.jdbc.core.convert; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import org.assertj.core.api.SoftAssertions; import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils; +import org.springframework.data.jdbc.testing.AnsiDialect; import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.data.relational.core.mapping.NamingStrategy; 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.relational.domain.SqlIdentifier.SimpleSqlIdentifier; /** * Unit tests the {@link SqlGenerator} with a fixed {@link NamingStrategy} implementation containing a hard wired @@ -39,31 +44,31 @@ public class SqlGeneratorFixedNamingStrategyUnitTests { final NamingStrategy fixedCustomTablePrefixStrategy = new NamingStrategy() { @Override - public String getSchema() { - return "FixedCustomSchema"; + public SqlIdentifier getSchema() { + return unquoted("FixedCustomSchema"); } @Override - public String getTableName(Class type) { - return "FixedCustomTablePrefix_" + type.getSimpleName(); + public SqlIdentifier getTableName(Class type) { + return unquoted("FixedCustomTablePrefix_" + type.getSimpleName()); } @Override - public String getColumnName(RelationalPersistentProperty property) { - return "FixedCustomPropertyPrefix_" + property.getName(); + public SimpleSqlIdentifier getColumnName(RelationalPersistentProperty property) { + return unquoted("FixedCustomPropertyPrefix_" + property.getName()); } }; final NamingStrategy upperCaseLowerCaseStrategy = new NamingStrategy() { @Override - public String getTableName(Class type) { - return type.getSimpleName().toUpperCase(); + public SqlIdentifier getTableName(Class type) { + return unquoted(type.getSimpleName().toUpperCase()); } @Override - public String getColumnName(RelationalPersistentProperty property) { - return property.getName().toLowerCase(); + public SimpleSqlIdentifier getColumnName(RelationalPersistentProperty property) { + return unquoted(property.getName().toLowerCase()); } }; @@ -83,8 +88,8 @@ public void findOneWithOverriddenFixedTableName() { "FixedCustomSchema.FixedCustomTablePrefix_DummyEntity.FixedCustomPropertyPrefix_id AS FixedCustomPropertyPrefix_id,") // .contains( "FixedCustomSchema.FixedCustomTablePrefix_DummyEntity.FixedCustomPropertyPrefix_name AS FixedCustomPropertyPrefix_name,") // - .contains("ref.FixedCustomPropertyPrefix_l1id AS ref_FixedCustomPropertyPrefix_l1id") // - .contains("ref.FixedCustomPropertyPrefix_content AS ref_FixedCustomPropertyPrefix_content") // + .contains("\"REF\".FixedCustomPropertyPrefix_l1id AS ref_FixedCustomPropertyPrefix_l1id") // + .contains("\"REF\".FixedCustomPropertyPrefix_content AS ref_FixedCustomPropertyPrefix_content") // .contains("FROM FixedCustomSchema.FixedCustomTablePrefix_DummyEntity"); softAssertions.assertAll(); } @@ -101,8 +106,8 @@ public void findOneWithUppercasedTablesAndLowercasedColumns() { .startsWith("SELECT") // .contains("DUMMYENTITY.id AS id,") // .contains("DUMMYENTITY.name AS name,") // - .contains("ref.l1id AS ref_l1id") // - .contains("ref.content AS ref_content") // + .contains("\"REF\".l1id AS ref_l1id") // + .contains("\"REF\".content AS ref_content") // .contains("FROM DUMMYENTITY"); softAssertions.assertAll(); } @@ -115,7 +120,7 @@ public void cascadingDeleteFirstLevel() { String sql = sqlGenerator.createDeleteByPath(getPath("ref")); assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " - + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.dummy_entity = :rootId"); + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.\"DUMMY_ENTITY\" = :rootId"); } @Test // DATAJDBC-107 @@ -126,10 +131,10 @@ public void cascadingDeleteAllSecondLevel() { String sql = sqlGenerator.createDeleteByPath(getPath("ref.further")); assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity " - + "WHERE FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity.referenced_entity IN " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity.\"REFERENCED_ENTITY\" IN " + "(SELECT FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.FixedCustomPropertyPrefix_l1id " + "FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " - + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.dummy_entity = :rootId)"); + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.\"DUMMY_ENTITY\" = :rootId)"); } @Test // DATAJDBC-107 @@ -150,7 +155,7 @@ public void cascadingDeleteAllFirstLevel() { String sql = sqlGenerator.createDeleteAllSql(getPath("ref")); assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " - + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.dummy_entity IS NOT NULL"); + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.\"DUMMY_ENTITY\" IS NOT NULL"); } @Test // DATAJDBC-107 @@ -161,10 +166,10 @@ public void cascadingDeleteSecondLevel() { String sql = sqlGenerator.createDeleteAllSql(getPath("ref.further")); assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity " - + "WHERE FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity.referenced_entity IN " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity.\"REFERENCED_ENTITY\" IN " + "(SELECT FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.FixedCustomPropertyPrefix_l1id " + "FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " - + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.dummy_entity IS NOT NULL)"); + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity.\"DUMMY_ENTITY\" IS NOT NULL)"); } @Test // DATAJDBC-113 @@ -189,7 +194,7 @@ private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { RelationalMappingContext context = new JdbcMappingContext(namingStrategy); RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); - return new SqlGenerator(context, persistentEntity); + return new SqlGenerator(context, persistentEntity, AnsiDialect.INSTANCE); } @SuppressWarnings("unused") diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java index 71578b5fc7..ebf0a96149 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java @@ -17,6 +17,7 @@ import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import java.util.Map; import java.util.Set; @@ -27,11 +28,17 @@ import org.springframework.data.annotation.Id; import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.annotation.Version; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.PropertyPathTestingUtils; import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils; +import org.springframework.data.jdbc.testing.AnsiDialect; +import org.springframework.data.jdbc.testing.NonQuotingDialect; import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; @@ -41,6 +48,7 @@ import org.springframework.data.relational.core.sql.Aliased; import org.springframework.data.relational.core.sql.Table; import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.relational.domain.SqlIdentifier.*; /** * Unit tests for the {@link SqlGenerator}. @@ -51,10 +59,11 @@ * @author Bastian Wilhelm * @author Mark Paluch * @author Tom Hombergs + * @author Milan Milanov */ public class SqlGeneratorUnitTests { - static final Identifier BACKREF = Identifier.of("backref", "some-value", String.class); + static final Identifier BACKREF = Identifier.of(unquoted("backref"), "some-value", String.class); SqlGenerator sqlGenerator; NamingStrategy namingStrategy = new PrefixingNamingStrategy(); @@ -67,9 +76,14 @@ public void setUp() { SqlGenerator createSqlGenerator(Class type) { + return createSqlGenerator(type, NonQuotingDialect.INSTANCE); + } + + SqlGenerator createSqlGenerator(Class type, Dialect dialect) { + RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(type); - return new SqlGenerator(context, persistentEntity); + return new SqlGenerator(context, persistentEntity, dialect); } @Test // DATAJDBC-112 @@ -150,6 +164,103 @@ public void deleteMapByPath() { assertThat(sql).isEqualTo("DELETE FROM element WHERE element.dummy_entity = :rootId"); } + @Test // DATAJDBC-101 + public void findAllSortedByUnsorted() { + + String sql = sqlGenerator.getFindAll(Sort.unsorted()); + + assertThat(sql).doesNotContain("ORDER BY"); + } + + @Test // DATAJDBC-101 + public void findAllSortedBySingleField() { + + String sql = sqlGenerator.getFindAll(Sort.by("x_name")); + + assertThat(sql).contains("SELECT", // + "dummy_entity.id1 AS id1", // + "dummy_entity.x_name AS x_name", // + "dummy_entity.x_other AS x_other", // + "ref.x_l1id AS ref_x_l1id", // + "ref.x_content AS ref_x_content", // + "ref_further.x_l2id AS ref_further_x_l2id", // + "ref_further.x_something AS ref_further_x_something", // + "FROM dummy_entity ", // + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1", // + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id", // + "ORDER BY x_name ASC"); + } + + @Test // DATAJDBC-101 + public void findAllSortedByMultipleFields() { + + String sql = sqlGenerator.getFindAll( + Sort.by(new Sort.Order(Sort.Direction.DESC, "x_name"), new Sort.Order(Sort.Direction.ASC, "x_other"))); + + assertThat(sql).contains("SELECT", // + "dummy_entity.id1 AS id1", // + "dummy_entity.x_name AS x_name", // + "dummy_entity.x_other AS x_other", // + "ref.x_l1id AS ref_x_l1id", // + "ref.x_content AS ref_x_content", // + "ref_further.x_l2id AS ref_further_x_l2id", // + "ref_further.x_something AS ref_further_x_something", // + "FROM dummy_entity ", // + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1", // + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id", // + "ORDER BY x_name DESC", // + "x_other ASC"); + } + + @Test // DATAJDBC-101 + public void findAllPagedByUnpaged() { + + String sql = sqlGenerator.getFindAll(Pageable.unpaged()); + + assertThat(sql).doesNotContain("ORDER BY").doesNotContain("FETCH FIRST").doesNotContain("OFFSET"); + } + + @Test // DATAJDBC-101 + public void findAllPaged() { + + String sql = sqlGenerator.getFindAll(PageRequest.of(2, 20)); + + assertThat(sql).contains("SELECT", // + "dummy_entity.id1 AS id1", // + "dummy_entity.x_name AS x_name", // + "dummy_entity.x_other AS x_other", // + "ref.x_l1id AS ref_x_l1id", // + "ref.x_content AS ref_x_content", // + "ref_further.x_l2id AS ref_further_x_l2id", // + "ref_further.x_something AS ref_further_x_something", // + "FROM dummy_entity ", // + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1", // + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id", // + "OFFSET 40 ROWS", // + "FETCH FIRST 20 ROWS ONLY"); + } + + @Test // DATAJDBC-101 + public void findAllPagedAndSorted() { + + String sql = sqlGenerator.getFindAll(PageRequest.of(3, 10, Sort.by("x_name"))); + + assertThat(sql).contains("SELECT", // + "dummy_entity.id1 AS id1", // + "dummy_entity.x_name AS x_name", // + "dummy_entity.x_other AS x_other", // + "ref.x_l1id AS ref_x_l1id", // + "ref.x_content AS ref_x_content", // + "ref_further.x_l2id AS ref_further_x_l2id", // + "ref_further.x_something AS ref_further_x_something", // + "FROM dummy_entity ", // + "LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.id1", // + "LEFT OUTER JOIN second_level_referenced_entity AS ref_further ON ref_further.referenced_entity = ref.x_l1id", // + "ORDER BY x_name ASC", // + "OFFSET 30 ROWS", // + "FETCH FIRST 10 ROWS ONLY"); + } + @Test // DATAJDBC-131, DATAJDBC-111 public void findAllByProperty() { @@ -174,8 +285,8 @@ public void findAllByProperty() { public void findAllByPropertyWithMultipartIdentifier() { // this would get called when ListParent is the element type of a Set - Identifier parentIdentifier = Identifier.of("backref", "some-value", String.class) // - .withPart("backref_key", "key-value", Object.class); + Identifier parentIdentifier = Identifier.of(unquoted("backref"), "some-value", String.class) // + .withPart(unquoted("backref_key"), "key-value", Object.class); String sql = sqlGenerator.getFindAllByProperty(parentIdentifier, null, false); assertThat(sql).contains("SELECT", // @@ -197,7 +308,7 @@ public void findAllByPropertyWithMultipartIdentifier() { public void findAllByPropertyWithKey() { // this would get called when ListParent is th element type of a Map - String sql = sqlGenerator.getFindAllByProperty(BACKREF, "key-column", false); + String sql = sqlGenerator.getFindAllByProperty(BACKREF, unquoted("key-column"), false); assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " // + "dummy_entity.x_other AS x_other, " // @@ -219,7 +330,7 @@ public void findAllByPropertyOrderedWithoutKey() { public void findAllByPropertyWithKeyOrdered() { // this would get called when ListParent is th element type of a Map - String sql = sqlGenerator.getFindAllByProperty(BACKREF, "key-column", true); + String sql = sqlGenerator.getFindAllByProperty(BACKREF, unquoted("key-column"), true); assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " // + "dummy_entity.x_other AS x_other, " // @@ -235,16 +346,16 @@ public void findAllByPropertyWithKeyOrdered() { @Test // DATAJDBC-219 public void updateWithVersion() { - SqlGenerator sqlGenerator = createSqlGenerator(VersionedEntity.class); + SqlGenerator sqlGenerator = createSqlGenerator(VersionedEntity.class, AnsiDialect.INSTANCE); assertThat(sqlGenerator.getUpdateWithVersion()).containsSequence( // "UPDATE", // - "versioned_entity", // + "\"VERSIONED_ENTITY\"", // "SET", // "WHERE", // - "id1 = :id", // + "\"ID1\" = :ID1", // "AND", // - "version = :___oldOptimisticLockingVersion"); + "\"X_VERSION\" = :___oldOptimisticLockingVersion"); } @Test // DATAJDBC-264 @@ -260,66 +371,69 @@ public void getInsertForEmptyColumnList() { @Test // DATAJDBC-334 public void getInsertForQuotedColumnName() { - SqlGenerator sqlGenerator = createSqlGenerator(EntityWithQuotedColumnName.class); + SqlGenerator sqlGenerator = createSqlGenerator(EntityWithQuotedColumnName.class, AnsiDialect.INSTANCE); String insert = sqlGenerator.getInsert(emptySet()); - assertThat(insert) - .isEqualTo("INSERT INTO entity_with_quoted_column_name " + "(\"test_@123\") " + "VALUES (:test_123)"); + assertThat(insert).isEqualTo("INSERT INTO \"ENTITY_WITH_QUOTED_COLUMN_NAME\" " // + + "(\"TEST\"\"_@123\") " + "VALUES (:TEST_123)"); } @Test // DATAJDBC-266 public void joinForOneToOneWithoutIdIncludesTheBackReferenceOfTheOuterJoin() { - SqlGenerator sqlGenerator = createSqlGenerator(ParentOfNoIdChild.class); + SqlGenerator sqlGenerator = createSqlGenerator(ParentOfNoIdChild.class, AnsiDialect.INSTANCE); String findAll = sqlGenerator.getFindAll(); - assertThat(findAll).containsSequence("SELECT", "child.parent_of_no_id_child AS child_parent_of_no_id_child", - "FROM"); + assertThat(findAll).containsSequence("SELECT", + "\"CHILD\".\"PARENT_OF_NO_ID_CHILD\" AS \"CHILD_PARENT_OF_NO_ID_CHILD\"", "FROM"); } @Test // DATAJDBC-262 public void update() { + SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class, AnsiDialect.INSTANCE); + assertThat(sqlGenerator.getUpdate()).containsSequence( // "UPDATE", // - "dummy_entity", // + "\"DUMMY_ENTITY\"", // "SET", // "WHERE", // - "id1 = :id"); + "\"ID1\" = :ID"); } @Test // DATAJDBC-324 public void readOnlyPropertyExcludedFromQuery_when_generateUpdateSql() { - final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); + final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class, AnsiDialect.INSTANCE); assertThat(sqlGenerator.getUpdate()).isEqualToIgnoringCase( // - "UPDATE entity_with_read_only_property " // - + "SET x_name = :x_name " // - + "WHERE entity_with_read_only_property.x_id = :x_id" // + "UPDATE \"ENTITY_WITH_READ_ONLY_PROPERTY\" " // + + "SET \"X_NAME\" = :X_NAME " // + + "WHERE \"ENTITY_WITH_READ_ONLY_PROPERTY\".\"X_ID\" = :X_ID" // ); } @Test // DATAJDBC-334 public void getUpdateForQuotedColumnName() { - SqlGenerator sqlGenerator = createSqlGenerator(EntityWithQuotedColumnName.class); + SqlGenerator sqlGenerator = createSqlGenerator(EntityWithQuotedColumnName.class, AnsiDialect.INSTANCE); String update = sqlGenerator.getUpdate(); - assertThat(update).isEqualTo("UPDATE entity_with_quoted_column_name " + "SET \"test_@123\" = :test_123 " - + "WHERE entity_with_quoted_column_name.\"test_@id\" = :test_id"); + assertThat(update).isEqualTo("UPDATE \"ENTITY_WITH_QUOTED_COLUMN_NAME\" " // + + "SET \"TEST\"\"_@123\" = :TEST_123 " // + + "WHERE \"ENTITY_WITH_QUOTED_COLUMN_NAME\".\"TEST\"\"_@ID\" = :TEST_ID"); } @Test // DATAJDBC-324 public void readOnlyPropertyExcludedFromQuery_when_generateInsertSql() { - final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); + final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class, AnsiDialect.INSTANCE); assertThat(sqlGenerator.getInsert(emptySet())).isEqualToIgnoringCase( // - "INSERT INTO entity_with_read_only_property (x_name) " // + "INSERT INTO \"ENTITY_WITH_READ_ONLY_PROPERTY\" (\"X_NAME\") " // + "VALUES (:x_name)" // ); } @@ -340,7 +454,7 @@ public void readOnlyPropertyIncludedIntoQuery_when_generateFindAllByPropertySql( final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); - assertThat(sqlGenerator.getFindAllByProperty(BACKREF, "key-column", true)).isEqualToIgnoringCase( // + assertThat(sqlGenerator.getFindAllByProperty(BACKREF, unquoted("key-column"), true)).isEqualToIgnoringCase( // "SELECT " // + "entity_with_read_only_property.x_id AS x_id, " // + "entity_with_read_only_property.x_name AS x_name, " // @@ -434,14 +548,15 @@ public void noJoinForSimpleColumn() { @Test // DATAJDBC-340 public void joinForSimpleReference() { + SqlGenerator.Join join = generateJoin("ref", DummyEntity.class); + SoftAssertions.assertSoftly(softly -> { - SqlGenerator.Join join = generateJoin("ref", DummyEntity.class); - softly.assertThat(join.getJoinTable().getName()).isEqualTo("referenced_entity"); + softly.assertThat(join.getJoinTable().getName()).isEqualTo("\"REFERENCED_ENTITY\""); softly.assertThat(join.getJoinColumn().getTable()).isEqualTo(join.getJoinTable()); - softly.assertThat(join.getJoinColumn().getName()).isEqualTo("dummy_entity"); - softly.assertThat(join.getParentId().getName()).isEqualTo("id1"); - softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("dummy_entity"); + softly.assertThat(join.getJoinColumn().getName()).isEqualTo("\"DUMMY_ENTITY\""); + softly.assertThat(join.getParentId().getName()).isEqualTo("\"ID1\""); + softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("\"DUMMY_ENTITY\""); }); } @@ -465,37 +580,39 @@ public void noJoinForMappedReference() { @Test // DATAJDBC-340 public void joinForSecondLevelReference() { + SqlGenerator.Join join = generateJoin("ref.further", DummyEntity.class); + SoftAssertions.assertSoftly(softly -> { - SqlGenerator.Join join = generateJoin("ref.further", DummyEntity.class); - softly.assertThat(join.getJoinTable().getName()).isEqualTo("second_level_referenced_entity"); + softly.assertThat(join.getJoinTable().getName()).isEqualTo("\"SECOND_LEVEL_REFERENCED_ENTITY\""); softly.assertThat(join.getJoinColumn().getTable()).isEqualTo(join.getJoinTable()); - softly.assertThat(join.getJoinColumn().getName()).isEqualTo("referenced_entity"); - softly.assertThat(join.getParentId().getName()).isEqualTo("x_l1id"); - softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("referenced_entity"); + softly.assertThat(join.getJoinColumn().getName()).isEqualTo("\"REFERENCED_ENTITY\""); + softly.assertThat(join.getParentId().getName()).isEqualTo("\"X_L1ID\""); + softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("\"REFERENCED_ENTITY\""); }); } @Test // DATAJDBC-340 public void joinForOneToOneWithoutId() { + SqlGenerator.Join join = generateJoin("child", ParentOfNoIdChild.class); + Table joinTable = join.getJoinTable(); + SoftAssertions.assertSoftly(softly -> { - SqlGenerator.Join join = generateJoin("child", ParentOfNoIdChild.class); - Table joinTable = join.getJoinTable(); - softly.assertThat(joinTable.getName()).isEqualTo("no_id_child"); + softly.assertThat(joinTable.getName()).isEqualTo("\"NO_ID_CHILD\""); softly.assertThat(joinTable).isInstanceOf(Aliased.class); - softly.assertThat(((Aliased) joinTable).getAlias()).isEqualTo("child"); + softly.assertThat(((Aliased) joinTable).getAlias()).isEqualTo("\"CHILD\""); softly.assertThat(join.getJoinColumn().getTable()).isEqualTo(joinTable); - softly.assertThat(join.getJoinColumn().getName()).isEqualTo("parent_of_no_id_child"); - softly.assertThat(join.getParentId().getName()).isEqualTo("x_id"); - softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("parent_of_no_id_child"); + softly.assertThat(join.getJoinColumn().getName()).isEqualTo("\"PARENT_OF_NO_ID_CHILD\""); + softly.assertThat(join.getParentId().getName()).isEqualTo("\"X_ID\""); + softly.assertThat(join.getParentId().getTable().getName()).isEqualTo("\"PARENT_OF_NO_ID_CHILD\""); }); } private SqlGenerator.Join generateJoin(String path, Class type) { - return createSqlGenerator(type) + return createSqlGenerator(type, AnsiDialect.INSTANCE) .getJoin(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context))); } @@ -504,7 +621,7 @@ public void simpleColumn() { assertThat(generatedColumn("id", DummyEntity.class)) // .extracting(c -> c.getName(), c -> c.getTable().getName(), c -> getAlias(c.getTable()), this::getAlias) - .containsExactly("id1", "dummy_entity", null, "id1"); + .containsExactly("\"ID1\"", "\"DUMMY_ENTITY\"", null, "\"ID1\""); } @Test // DATAJDBC-340 @@ -512,7 +629,7 @@ public void columnForIndirectProperty() { assertThat(generatedColumn("ref.l1id", DummyEntity.class)) // .extracting(c -> c.getName(), c -> c.getTable().getName(), c -> getAlias(c.getTable()), this::getAlias) // - .containsExactly("x_l1id", "referenced_entity", "ref", "ref_x_l1id"); + .containsExactly("\"X_L1ID\"", "\"REFERENCED_ENTITY\"", "\"REF\"", "\"REF_X_L1ID\""); } @Test // DATAJDBC-340 @@ -526,7 +643,8 @@ public void columnForReferencedEntityWithoutId() { assertThat(generatedColumn("child", ParentOfNoIdChild.class)) // .extracting(c -> c.getName(), c -> c.getTable().getName(), c -> getAlias(c.getTable()), this::getAlias) // - .containsExactly("parent_of_no_id_child", "no_id_child", "child", "child_parent_of_no_id_child"); + .containsExactly("\"PARENT_OF_NO_ID_CHILD\"", "\"NO_ID_CHILD\"", "\"CHILD\"", + "\"CHILD_PARENT_OF_NO_ID_CHILD\""); } private String getAlias(Object maybeAliased) { @@ -539,7 +657,7 @@ private String getAlias(Object maybeAliased) { private org.springframework.data.relational.core.sql.Column generatedColumn(String path, Class type) { - return createSqlGenerator(type) + return createSqlGenerator(type, AnsiDialect.INSTANCE) .getColumn(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context))); } @@ -599,8 +717,8 @@ static class OtherAggregate { private static class PrefixingNamingStrategy implements NamingStrategy { @Override - public String getColumnName(RelationalPersistentProperty property) { - return "x_" + NamingStrategy.super.getColumnName(property); + public SimpleSqlIdentifier getColumnName(RelationalPersistentProperty property) { + return NamingStrategy.super.getColumnName(property).prefix("x_"); } } @@ -621,8 +739,10 @@ static class EntityWithReadOnlyProperty { static class EntityWithQuotedColumnName { - @Id @Column("\"test_@id\"") Long id; - @Column("\"test_@123\"") String name; + // these column names behave like single double quote in the name since the get quoted and then doubling the double + // quote escapes it. + @Id @Column("test\"\"_@id") Long id; + @Column("test\"\"_@123") String name; } @SuppressWarnings("unused") diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSourceUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSourceUnitTests.java new file mode 100644 index 0000000000..f4aaed9bde --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSourceUnitTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.core.convert; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Test; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.SqlIdentifier; + +/** + * Tests for {@link SqlIdentifierParameterSource}. + * + * @author Jens Schauder + */ +public class SqlIdentifierParameterSourceUnitTests { + + private IdentifierProcessing identifierProcessing = IdentifierProcessing.ANSI; + + @Test // DATAJDBC-386 + public void empty() { + + SqlIdentifierParameterSource parameters = new SqlIdentifierParameterSource(identifierProcessing); + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(parameters.getParameterNames()).isEmpty(); + softly.assertThat(parameters.getValue("blah")).isNull(); + softly.assertThat(parameters.hasValue("blah")).isFalse(); + softly.assertThat(parameters.getSqlType("blah")).isEqualTo(Integer.MIN_VALUE); + }); + } + + @Test // DATAJDBC-386 + public void addSingleValue() { + + SqlIdentifierParameterSource parameters = new SqlIdentifierParameterSource(identifierProcessing); + + parameters.addValue(SqlIdentifier.unquoted("key"), 23); + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(parameters.getParameterNames()).isEqualTo(new String[] { "key" }); + softly.assertThat(parameters.getValue("key")).isEqualTo(23); + softly.assertThat(parameters.hasValue("key")).isTrue(); + + softly.assertThat(parameters.getValue("blah")).isNull(); + softly.assertThat(parameters.hasValue("blah")).isFalse(); + softly.assertThat(parameters.getSqlType("blah")).isEqualTo(Integer.MIN_VALUE); + }); + } + + @Test // DATAJDBC-386 + public void addSingleValueWithType() { + + SqlIdentifierParameterSource parameters = new SqlIdentifierParameterSource(identifierProcessing); + + parameters.addValue(SqlIdentifier.unquoted("key"), 23, 42); + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(parameters.getParameterNames()).isEqualTo(new String[] { "key" }); + softly.assertThat(parameters.getValue("key")).isEqualTo(23); + softly.assertThat(parameters.hasValue("key")).isTrue(); + softly.assertThat(parameters.getSqlType("key")).isEqualTo(42); + + softly.assertThat(parameters.getValue("blah")).isNull(); + softly.assertThat(parameters.hasValue("blah")).isFalse(); + softly.assertThat(parameters.getSqlType("blah")).isEqualTo(Integer.MIN_VALUE); + }); + } + + @Test // DATAJDBC-386 + public void addOtherDatabaseObjectIdentifierParameterSource() { + + SqlIdentifierParameterSource parameters = new SqlIdentifierParameterSource(identifierProcessing); + parameters.addValue(SqlIdentifier.unquoted("key1"), 111, 11); + parameters.addValue(SqlIdentifier.unquoted("key2"), 111); + + SqlIdentifierParameterSource parameters2 = new SqlIdentifierParameterSource(identifierProcessing); + parameters2.addValue(SqlIdentifier.unquoted("key2"), 222, 22); + parameters2.addValue(SqlIdentifier.unquoted("key3"), 222); + + parameters.addAll(parameters2); + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(parameters.getParameterNames()).isEqualTo(new String[] { "key1", "key2", "key3" }); + softly.assertThat(parameters.getValue("key1")).isEqualTo(111); + softly.assertThat(parameters.hasValue("key1")).isTrue(); + softly.assertThat(parameters.getSqlType("key1")).isEqualTo(11); + + softly.assertThat(parameters.getValue("key2")).isEqualTo(222); + softly.assertThat(parameters.hasValue("key2")).isTrue(); + softly.assertThat(parameters.getSqlType("key2")).isEqualTo(22); + + softly.assertThat(parameters.getValue("key3")).isEqualTo(222); + softly.assertThat(parameters.hasValue("key3")).isTrue(); + softly.assertThat(parameters.getSqlType("key3")).isEqualTo(Integer.MIN_VALUE); + + softly.assertThat(parameters.getValue("blah")).isNull(); + softly.assertThat(parameters.hasValue("blah")).isFalse(); + softly.assertThat(parameters.getSqlType("blah")).isEqualTo(Integer.MIN_VALUE); + }); + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java index 8da67ed849..de3ef499ce 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java @@ -15,7 +15,8 @@ */ package org.springframework.data.jdbc.core.mapping; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import lombok.Data; @@ -27,7 +28,6 @@ import org.assertj.core.api.SoftAssertions; import org.junit.Test; - import org.springframework.data.annotation.Id; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.relational.core.mapping.BasicRelationalPersistentProperty; @@ -89,9 +89,9 @@ public void detectsAnnotatedColumnName() { RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); - assertThat(entity.getRequiredPersistentProperty("name").getColumnName()).isEqualTo("dummy_name"); + assertThat(entity.getRequiredPersistentProperty("name").getColumnName()).isEqualTo(quoted("dummy_name")); assertThat(entity.getRequiredPersistentProperty("localDateTime").getColumnName()) - .isEqualTo("dummy_last_updated_at"); + .isEqualTo(quoted("dummy_last_updated_at")); } @Test // DATAJDBC-218 @@ -101,8 +101,8 @@ public void detectsAnnotatedColumnAndKeyName() { .getRequiredPersistentEntity(DummyEntity.class) // .getRequiredPersistentProperty("someList"); - assertThat(listProperty.getReverseColumnName()).isEqualTo("dummy_column_name"); - assertThat(listProperty.getKeyColumn()).isEqualTo("dummy_key_column_name"); + assertThat(listProperty.getReverseColumnName()).isEqualTo(quoted("dummy_column_name")); + assertThat(listProperty.getKeyColumn()).isEqualTo(quoted("dummy_key_column_name")); } @Test // DATAJDBC-221 @@ -125,8 +125,8 @@ public void detectsKeyColumnNameFromColumnAnnotation() { .getRequiredPersistentEntity(WithCollections.class) // .getRequiredPersistentProperty("someList"); - assertThat(listProperty.getKeyColumn()).isEqualTo("some_key"); - assertThat(listProperty.getReverseColumnName()).isEqualTo("some_value"); + assertThat(listProperty.getKeyColumn()).isEqualTo(quoted("some_key")); + assertThat(listProperty.getReverseColumnName()).isEqualTo(quoted("some_value")); } @Test // DATAJDBC-331 @@ -136,8 +136,8 @@ public void detectsKeyColumnOverrideNameFromMappedCollectionAnnotation() { .getRequiredPersistentEntity(WithCollections.class) // .getRequiredPersistentProperty("overrideList"); - assertThat(listProperty.getKeyColumn()).isEqualTo("override_key"); - assertThat(listProperty.getReverseColumnName()).isEqualTo("override_id"); + assertThat(listProperty.getKeyColumn()).isEqualTo(quoted("override_key")); + assertThat(listProperty.getReverseColumnName()).isEqualTo(quoted("override_id")); } private void checkTargetType(SoftAssertions softly, RelationalPersistentEntity persistentEntity, @@ -148,6 +148,11 @@ private void checkTargetType(SoftAssertions softly, RelationalPersistentEntity overrideList; } - - @SuppressWarnings("unused") - private enum SomeEnum { - ALPHA - } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mapping/model/NamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mapping/model/NamingStrategyUnitTests.java index 37eed43a85..fb9ed0f273 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mapping/model/NamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mapping/model/NamingStrategyUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.mapping.model; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import lombok.Data; @@ -27,6 +28,7 @@ import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.domain.SqlIdentifier; /** * Unit tests for the default {@link NamingStrategy}. @@ -44,42 +46,42 @@ public class NamingStrategyUnitTests { @Test // DATAJDBC-184 public void getTableName() { - assertThat(target.getTableName(persistentEntity.getType())).isEqualTo("dummy_entity"); + assertThat(target.getTableName(persistentEntity.getType())).isEqualTo(quoted("dummy_entity")); } @Test // DATAJDBC-184 public void getColumnName() { assertThat(target.getColumnName(persistentEntity.getPersistentProperty("id"))) // - .isEqualTo("id"); + .isEqualTo(quoted("id")); assertThat(target.getColumnName(persistentEntity.getPersistentProperty("createdAt"))) // - .isEqualTo("created_at"); + .isEqualTo(quoted("created_at")); assertThat(target.getColumnName(persistentEntity.getPersistentProperty("dummySubEntities"))) // - .isEqualTo("dummy_sub_entities"); + .isEqualTo(quoted("dummy_sub_entities")); } @Test // DATAJDBC-184 public void getReverseColumnName() { assertThat(target.getReverseColumnName(persistentEntity.getPersistentProperty("dummySubEntities"))) - .isEqualTo("dummy_entity"); + .isEqualTo(quoted("dummy_entity")); } @Test // DATAJDBC-184 public void getKeyColumn() { assertThat(target.getKeyColumn(persistentEntity.getPersistentProperty("dummySubEntities"))) // - .isEqualTo("dummy_entity_key"); + .isEqualTo(quoted("dummy_entity_key")); } @Test // DATAJDBC-184 public void getSchema() { - assertThat(target.getSchema()).isEmpty(); + assertThat(target.getSchema()).isEqualTo(SqlIdentifier.EMPTY); } @Test // DATAJDBC-184 public void getQualifiedTableName() { - assertThat(target.getQualifiedTableName(persistentEntity.getType())).isEqualTo("dummy_entity"); + assertThat(target.getQualifiedTableName(persistentEntity.getType())).isEqualTo(quoted("dummy_entity")); } @Data diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java index 62e41dd93f..48c6ffea13 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java @@ -15,7 +15,7 @@ */ package org.springframework.data.jdbc.mybatis; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import junit.framework.AssertionFailedError; @@ -39,6 +39,7 @@ import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; @@ -79,6 +80,8 @@ public void myBatisGetsUsedForInsertAndSelect() { assertThat(reloaded.name).isEqualTo("name " + saved.id); } + interface DummyEntityRepository extends CrudRepository {} + @org.springframework.context.annotation.Configuration @Import(TestConfiguration.class) @EnableJdbcRepositories(considerNestedRepositories = true) @@ -116,8 +119,9 @@ MyBatisDataAccessStrategy dataAccessStrategy(SqlSession sqlSession) { RelationalMappingContext context = new JdbcMappingContext(); JdbcConverter converter = new BasicJdbcConverter(context, (Identifier, path) -> null); - - MyBatisDataAccessStrategy strategy = new MyBatisDataAccessStrategy(sqlSession, context, converter); + + MyBatisDataAccessStrategy strategy = new MyBatisDataAccessStrategy(sqlSession, + HsqlDbDialect.INSTANCE.getIdentifierProcessing()); strategy.setNamespaceStrategy(new NamespaceStrategy() { @Override @@ -129,6 +133,4 @@ public String getNamespace(Class domainType) { return strategy; } } - - interface DummyEntityRepository extends CrudRepository {} } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategyUnitTests.java index 8ad76012c3..0006a7edee 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategyUnitTests.java @@ -15,18 +15,12 @@ */ package org.springframework.data.jdbc.mybatis; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static java.util.Arrays.*; +import static java.util.Collections.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import java.util.Collections; @@ -36,6 +30,8 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.PropertyPathTestingUtils; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -44,6 +40,7 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.Identifier; +import org.springframework.data.relational.domain.IdentifierProcessing; /** * Unit tests for the {@link MyBatisDataAccessStrategy}, mainly ensuring that the correct statements get's looked up. @@ -60,7 +57,7 @@ public class MyBatisDataAccessStrategyUnitTests { SqlSession session = mock(SqlSession.class); ArgumentCaptor captor = ArgumentCaptor.forClass(MyBatisContext.class); - MyBatisDataAccessStrategy accessStrategy = new MyBatisDataAccessStrategy(session, context, converter); + MyBatisDataAccessStrategy accessStrategy = new MyBatisDataAccessStrategy(session, IdentifierProcessing.ANSI); PersistentPropertyPath path = PropertyPathTestingUtils.toPath("one.two", DummyEntity.class, context); @@ -74,7 +71,7 @@ public void before() { @Test // DATAJDBC-123 public void insert() { - accessStrategy.insert("x", String.class, Collections.singletonMap("key", "value")); + accessStrategy.insert("x", String.class, Collections.singletonMap(unquoted("key"), "value")); verify(session).insert(eq("java.lang.StringMapper.insert"), captor.capture()); @@ -405,6 +402,50 @@ public void count() { ); } + @Test // DATAJDBC-101 + public void findAllSorted() { + + accessStrategy.findAll(String.class, Sort.by("length")); + + verify(session).selectList(eq("java.lang.StringMapper.findAllSorted"), captor.capture()); + + assertThat(captor.getValue()) // + .isNotNull() // + .extracting( // + MyBatisContext::getInstance, // + MyBatisContext::getId, // + MyBatisContext::getDomainType, // + c -> c.get("sort") // + ).containsExactly( // + null, // + null, // + String.class, // + Sort.by("length") // + ); + } + + @Test // DATAJDBC-101 + public void findAllPaged() { + + accessStrategy.findAll(String.class, PageRequest.of(0, 20)); + + verify(session).selectList(eq("java.lang.StringMapper.findAllPaged"), captor.capture()); + + assertThat(captor.getValue()) // + .isNotNull() // + .extracting( // + MyBatisContext::getInstance, // + MyBatisContext::getId, // + MyBatisContext::getDomainType, // + c -> c.get("pageable") // + ).containsExactly( // + null, // + null, // + String.class, // + PageRequest.of(0, 20) // + ); + } + @SuppressWarnings("unused") private static class DummyEntity { ChildOne one; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java index 93046a7c24..aae85c63e6 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java @@ -35,6 +35,7 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -57,6 +58,36 @@ @Transactional public class MyBatisHsqlIntegrationTests { + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + @Autowired SqlSessionFactory sqlSessionFactory; + @Autowired DummyEntityRepository repository; + + @Test // DATAJDBC-123 + public void mybatisSelfTest() { + + SqlSession session = sqlSessionFactory.openSession(); + + session.selectList("org.springframework.data.jdbc.mybatis.DummyEntityMapper.findById"); + } + + @Test // DATAJDBC-123 + public void myBatisGetsUsedForInsertAndSelect() { + + DummyEntity entity = new DummyEntity(null, "some name"); + DummyEntity saved = repository.save(entity); + + assertThat(saved.id).isNotNull(); + + DummyEntity reloaded = repository.findById(saved.id).orElseThrow(AssertionFailedError::new); + + assertThat(reloaded).isNotNull().extracting(e -> e.id, e -> e.name); + } + + interface DummyEntityRepository extends CrudRepository { + + } + @org.springframework.context.annotation.Configuration @Import(TestConfiguration.class) @EnableJdbcRepositories(considerNestedRepositories = true) @@ -94,38 +125,7 @@ DataAccessStrategy dataAccessStrategy(RelationalMappingContext context, JdbcConv SqlSession sqlSession, EmbeddedDatabase db) { return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, converter, - new NamedParameterJdbcTemplate(db), sqlSession); + new NamedParameterJdbcTemplate(db), sqlSession, HsqlDbDialect.INSTANCE); } } - - @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); - @Rule public SpringMethodRule methodRule = new SpringMethodRule(); - - @Autowired SqlSessionFactory sqlSessionFactory; - @Autowired DummyEntityRepository repository; - - @Test // DATAJDBC-123 - public void mybatisSelfTest() { - - SqlSession session = sqlSessionFactory.openSession(); - - session.selectList("org.springframework.data.jdbc.mybatis.DummyEntityMapper.findById"); - } - - @Test // DATAJDBC-123 - public void myBatisGetsUsedForInsertAndSelect() { - - DummyEntity entity = new DummyEntity(null, "some name"); - DummyEntity saved = repository.save(entity); - - assertThat(saved.id).isNotNull(); - - DummyEntity reloaded = repository.findById(saved.id).orElseThrow(AssertionFailedError::new); - - assertThat(reloaded).isNotNull().extracting(e -> e.id, e -> e.name); - } - - interface DummyEntityRepository extends CrudRepository { - - } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java index d4fd91a9be..40583b71f5 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java @@ -237,17 +237,18 @@ static class DummyEntity { String test; - @Column("id") + @Column("ID") DummyEntity2 dummyEntity2; } @Data static class DummyEntity2 { + @Column("ID") @Id Long id; String test; - @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "PREFIX_") Embeddable embeddable; } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java index 3561e0129e..087b74f2d2 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests.java @@ -250,13 +250,13 @@ private static class DummyEntity { String test; - @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") + @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "PREFIX_") Embeddable embeddable; } @Data private static class Embeddable { - @MappedCollection(idColumn = "id", keyColumn = "order_key") + @MappedCollection(idColumn = "ID", keyColumn = "ORDER_KEY") List list = new ArrayList<>(); String test; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java index c219759faa..b03bf91c22 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java @@ -244,7 +244,7 @@ private static class DummyEntity { @Data private static class Embeddable { - @Column("id") + @Column("ID") DummyEntity2 dummyEntity2; String test; @@ -253,6 +253,7 @@ private static class Embeddable { @Data private static class DummyEntity2 { + @Column("ID") @Id Long id; String test; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java index 634ec747b1..baace123d9 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.repository; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import lombok.Data; import lombok.Value; @@ -27,7 +28,6 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -39,6 +39,7 @@ import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.context.ContextConfiguration; @@ -166,8 +167,8 @@ NamingStrategy namingStrategy() { return new NamingStrategy() { @Override - public String getTableName(Class type) { - return type.getSimpleName().toUpperCase(); + public SqlIdentifier getTableName(Class type) { + return unquoted(type.getSimpleName().toUpperCase()); } }; } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java index 49a35de4ec..e6f11a4be3 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -35,6 +35,9 @@ import org.mockito.stubbing.Answer; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory; @@ -45,6 +48,7 @@ import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; +import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.event.AfterDeleteEvent; import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; @@ -53,7 +57,7 @@ import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent; import org.springframework.data.relational.core.mapping.event.Identifier; import org.springframework.data.relational.core.mapping.event.RelationalEvent; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; @@ -66,6 +70,7 @@ * @author Mark Paluch * @author Oliver Gierke * @author Myeonghyeon Lee + * @author Milan Milanov */ public class SimpleJdbcRepositoryEventsUnitTests { @@ -82,7 +87,7 @@ public void before() { DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy(); JdbcConverter converter = new BasicJdbcConverter(context, delegatingDataAccessStrategy, new JdbcCustomConversions(), new DefaultJdbcTypeFactory(operations.getJdbcOperations())); - SqlGeneratorSource generatorSource = new SqlGeneratorSource(context); + SqlGeneratorSource generatorSource = new SqlGeneratorSource(context, HsqlDbDialect.INSTANCE); this.dataAccessStrategy = spy(new DefaultDataAccessStrategy(generatorSource, context, converter, operations)); delegatingDataAccessStrategy.setDelegate(dataAccessStrategy); @@ -215,6 +220,45 @@ public void publishesEventsOnFindById() { ); } + @Test // DATAJDBC-101 + @SuppressWarnings("rawtypes") + public void publishesEventsOnFindAllSorted() { + + DummyEntity entity1 = new DummyEntity(42L); + DummyEntity entity2 = new DummyEntity(23L); + + doReturn(asList(entity1, entity2)).when(dataAccessStrategy).findAll(any(), any(Sort.class)); + + repository.findAll(Sort.by("field")); + + assertThat(publisher.events) // + .extracting(e -> (Class) e.getClass()) // + .containsExactly( // + AfterLoadEvent.class, // + AfterLoadEvent.class // + ); + } + + @Test // DATAJDBC-101 + @SuppressWarnings("rawtypes") + public void publishesEventsOnFindAllPaged() { + + DummyEntity entity1 = new DummyEntity(42L); + DummyEntity entity2 = new DummyEntity(23L); + + doReturn(asList(entity1, entity2)).when(dataAccessStrategy).findAll(any(), any(Pageable.class)); + doReturn(2L).when(dataAccessStrategy).count(any()); + + repository.findAll(PageRequest.of(0, 20)); + + assertThat(publisher.events) // + .extracting(e -> (Class) e.getClass()) // + .containsExactly( // + AfterLoadEvent.class, // + AfterLoadEvent.class // + ); + } + private static NamedParameterJdbcOperations createIdGeneratingOperations() { Answer setIdInKeyHolder = invocation -> { @@ -234,7 +278,7 @@ private static NamedParameterJdbcOperations createIdGeneratingOperations() { return operations; } - interface DummyEntityRepository extends CrudRepository {} + interface DummyEntityRepository extends PagingAndSortingRepository {} @Value @With diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java index 4dbf6601bc..53845fa49b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.repository.config; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import lombok.Data; @@ -44,6 +45,7 @@ import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.springframework.test.context.ActiveProfiles; @@ -271,8 +273,8 @@ NamingStrategy namingStrategy() { return new NamingStrategy() { - public String getTableName(@NotNull Class type) { - return "DummyEntity"; + public SqlIdentifier getTableName(@NotNull Class type) { + return unquoted("DummyEntity"); } }; } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java index e265f72dd4..79d3dfc94c 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java @@ -40,9 +40,9 @@ import org.springframework.data.jdbc.repository.QueryMappingConfiguration; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean; +import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.CrudRepository; -import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -69,7 +69,6 @@ public class EnableJdbcRepositoriesIntegrationTests { "dataAccessStrategy"); public static final RowMapper DUMMY_ENTITY_ROW_MAPPER = mock(RowMapper.class); public static final RowMapper STRING_ROW_MAPPER = mock(RowMapper.class); - public static final ResultSetExtractor INTEGER_RESULT_SET_EXTRACTOR = mock(ResultSetExtractor.class); @Autowired JdbcRepositoryFactoryBean factoryBean; @Autowired DummyRepository repository; @@ -153,8 +152,8 @@ NamedParameterJdbcOperations qualifierJdbcOperations(DataSource dataSource) { @Bean("qualifierDataAccessStrategy") DataAccessStrategy defaultDataAccessStrategy( @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template, - RelationalMappingContext context, JdbcConverter converter) { - return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, template); + RelationalMappingContext context, JdbcConverter converter, Dialect dialect) { + return new DefaultDataAccessStrategy(new SqlGeneratorSource(context, dialect), context, converter, template); } } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java index 15a0b266d7..b85ad3db9d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java @@ -37,6 +37,7 @@ import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.repository.QueryMappingConfiguration; +import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -60,6 +61,8 @@ public class JdbcRepositoryFactoryBeanUnitTests { @Mock DataAccessStrategy dataAccessStrategy; @Mock ApplicationEventPublisher publisher; @Mock(answer = Answers.RETURNS_DEEP_STUBS) ListableBeanFactory beanFactory; + @Mock + Dialect dialect; RelationalMappingContext mappingContext; @@ -86,6 +89,7 @@ public void setsUpBasicInstanceCorrectly() { factoryBean.setConverter(new BasicJdbcConverter(mappingContext, dataAccessStrategy)); factoryBean.setApplicationEventPublisher(publisher); factoryBean.setBeanFactory(beanFactory); + factoryBean.setDialect(dialect); factoryBean.afterPropertiesSet(); assertThat(factoryBean.getObject()).isNotNull(); @@ -113,6 +117,7 @@ public void afterPropertiesSetDefaultsNullablePropertiesCorrectly() { factoryBean.setConverter(new BasicJdbcConverter(mappingContext, dataAccessStrategy)); factoryBean.setApplicationEventPublisher(publisher); factoryBean.setBeanFactory(beanFactory); + factoryBean.setDialect(dialect); factoryBean.afterPropertiesSet(); assertThat(factoryBean.getObject()).isNotNull(); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/AnsiDialect.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/AnsiDialect.java new file mode 100644 index 0000000000..be3814e025 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/AnsiDialect.java @@ -0,0 +1,130 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.testing; + +import lombok.RequiredArgsConstructor; + +import org.springframework.data.relational.core.dialect.AbstractDialect; +import org.springframework.data.relational.core.dialect.ArrayColumns; +import org.springframework.data.relational.core.dialect.LimitClause; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * An SQL dialect for the ANSI SQL standard. + * + * @author Milan Milanov + * @since 2.0 + */ +public class AnsiDialect extends AbstractDialect { + + /** + * Singleton instance. + */ + public static final AnsiDialect INSTANCE = new AnsiDialect(); + + protected AnsiDialect() {} + + private static final LimitClause LIMIT_CLAUSE = new LimitClause() { + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.dialect.LimitClause#getLimit(long) + */ + @Override + public String getLimit(long limit) { + return String.format("FETCH FIRST %d ROWS ONLY", limit); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.dialect.LimitClause#getOffset(long) + */ + @Override + public String getOffset(long offset) { + return String.format("OFFSET %d ROWS", offset); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.dialect.LimitClause#getClause(long, long) + */ + @Override + public String getLimitOffset(long limit, long offset) { + return String.format("OFFSET %d ROWS FETCH FIRST %d ROWS ONLY", offset, limit); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.dialect.LimitClause#getClausePosition() + */ + @Override + public Position getClausePosition() { + return Position.AFTER_ORDER_BY; + } + }; + + private final AnsiArrayColumns ARRAY_COLUMNS = new AnsiArrayColumns(); + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.dialect.Dialect#limit() + */ + @Override + public LimitClause limit() { + return LIMIT_CLAUSE; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.dialect.Dialect#getArraySupport() + */ + @Override + public ArrayColumns getArraySupport() { + return ARRAY_COLUMNS; + } + + @RequiredArgsConstructor + static class AnsiArrayColumns implements ArrayColumns { + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.dialect.ArrayColumns#isSupported() + */ + @Override + public boolean isSupported() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.dialect.ArrayColumns#getArrayType(java.lang.Class) + */ + @Override + public Class getArrayType(Class userType) { + + Assert.notNull(userType, "Array component type must not be null"); + + return ClassUtils.resolvePrimitiveIfNecessary(userType); + } + } + + @Override + public IdentifierProcessing getIdentifierProcessing() { + return IdentifierProcessing.ANSI; + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java index 54e2f7cb0c..8463147ad5 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java @@ -21,6 +21,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; @@ -47,4 +49,9 @@ DataSource dataSource() { .addScript(TestUtils.createScriptName(context, "hsql")) // .build(); } + + @Bean + Dialect dialect() { + return HsqlDbDialect.INSTANCE; + } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MariaDBDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MariaDBDataSourceConfiguration.java index bfa9ac68e9..523f93f447 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MariaDBDataSourceConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MariaDBDataSourceConfiguration.java @@ -22,8 +22,11 @@ import javax.sql.DataSource; import org.mariadb.jdbc.MariaDbDataSource; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.MariaDbDialect; import org.testcontainers.containers.MariaDBContainer; import org.testcontainers.jdbc.ext.ScriptUtils; @@ -48,7 +51,9 @@ class MariaDBDataSourceConfiguration extends DataSourceConfiguration { */ @Override protected DataSource createDataSource() { + try { + MariaDbDataSource dataSource = new MariaDbDataSource(); dataSource.setUrl(MARIADB_CONTAINER.getJdbcUrl()); dataSource.setUser(MARIADB_CONTAINER.getUsername()); @@ -59,6 +64,11 @@ protected DataSource createDataSource() { } } + @Bean + Dialect dialect() { + return MariaDbDialect.INSTANCE; + } + @PostConstruct public void initDatabase() throws SQLException, ScriptException { ScriptUtils.executeSqlScript(createDataSource().getConnection(), null, "DROP DATABASE test;CREATE DATABASE test;"); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java index 4dc4212cc2..739d81f8a8 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java @@ -21,8 +21,11 @@ import javax.script.ScriptException; import javax.sql.DataSource; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.MySqlDialect; import org.testcontainers.containers.MySQLContainer; import org.testcontainers.jdbc.ext.ScriptUtils; @@ -62,6 +65,11 @@ protected DataSource createDataSource() { return dataSource; } + @Bean + Dialect dialect() { + return MySqlDialect.INSTANCE; + } + @PostConstruct public void initDatabase() throws SQLException, ScriptException { ScriptUtils.executeSqlScript(createDataSource().getConnection(), null, "DROP DATABASE test;CREATE DATABASE test;"); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/NonQuotingDialect.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/NonQuotingDialect.java new file mode 100644 index 0000000000..9a56096959 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/NonQuotingDialect.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.testing; + +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.IdentifierProcessing.DefaultIdentifierProcessing; + +/** + * The ANSI standard dialect, but without quoting the identifiers. + * + * @author Milan Milanov + * @since 2.0 + */ +public class NonQuotingDialect extends AnsiDialect { + + public static final NonQuotingDialect INSTANCE = new NonQuotingDialect(); + + @Override + public IdentifierProcessing getIdentifierProcessing() { + return new DefaultIdentifierProcessing(new IdentifierProcessing.Quoting(""), + IdentifierProcessing.LetterCasing.AS_IS); + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java index 7cbae7fec4..63d083cd92 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java @@ -18,8 +18,11 @@ import javax.sql.DataSource; import org.postgresql.ds.PGSimpleDataSource; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.PostgresDialect; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.testcontainers.containers.PostgreSQLContainer; @@ -55,6 +58,11 @@ protected DataSource createDataSource() { return dataSource; } + @Bean + Dialect dialect() { + return PostgresDialect.INSTANCE; + } + /* * (non-Javadoc) * @see org.springframework.data.jdbc.testing.DataSourceFactoryBean#customizePopulator(org.springframework.jdbc.datasource.init.ResourceDatabasePopulator) diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java index 623ddb9ba2..4c65f7e0b8 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -20,8 +20,6 @@ import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; @@ -40,7 +38,7 @@ import org.springframework.data.jdbc.core.convert.SqlGeneratorSource; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; -import org.springframework.data.mapping.callback.EntityCallbacks; +import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -84,10 +82,10 @@ PlatformTransactionManager transactionManager() { @Bean DataAccessStrategy defaultDataAccessStrategy( @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template, RelationalMappingContext context, - JdbcConverter converter) { + JdbcConverter converter, Dialect dialect) { - DefaultDataAccessStrategy defaultDataAccessStrategy = new DefaultDataAccessStrategy(new SqlGeneratorSource(context), - context, converter, template); + DefaultDataAccessStrategy defaultDataAccessStrategy = new DefaultDataAccessStrategy( + new SqlGeneratorSource(context, dialect), context, converter, template); return defaultDataAccessStrategy; } diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql index db71d7a10f..1ea550facf 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql @@ -1,292 +1,297 @@ CREATE TABLE LEGO_SET ( - id1 BIGINT AUTO_INCREMENT PRIMARY KEY, - NAME VARCHAR(30) + id1 BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(30) ); CREATE TABLE MANUAL ( - id2 BIGINT AUTO_INCREMENT PRIMARY KEY, - LEGO_SET BIGINT, - ALTERNATIVE BIGINT, - CONTENT VARCHAR(2000) + id2 BIGINT AUTO_INCREMENT PRIMARY KEY, + LEGO_SET BIGINT, + ALTERNATIVE BIGINT, + CONTENT VARCHAR(2000) ); ALTER TABLE MANUAL - ADD FOREIGN KEY (LEGO_SET) - REFERENCES LEGO_SET (id1); + ADD FOREIGN KEY (LEGO_SET) + REFERENCES LEGO_SET (id1); CREATE TABLE ONE_TO_ONE_PARENT ( - id3 BIGINT AUTO_INCREMENT PRIMARY KEY, - content VARCHAR(30) + id3 BIGINT AUTO_INCREMENT PRIMARY KEY, + content VARCHAR(30) ); CREATE TABLE Child_No_Id ( - ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, - content VARCHAR(30) + ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, + content VARCHAR(30) ); CREATE TABLE LIST_PARENT ( - id4 BIGINT AUTO_INCREMENT PRIMARY KEY, - NAME VARCHAR(100) + id4 BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(100) ); CREATE TABLE element_no_id ( - content VARCHAR(100), - LIST_PARENT_key BIGINT, - LIST_PARENT BIGINT + content VARCHAR(100), + LIST_PARENT_key BIGINT, + LIST_PARENT BIGINT ); CREATE TABLE BYTE_ARRAY_OWNER ( - ID BIGINT AUTO_INCREMENT PRIMARY KEY, - BINARY_DATA VARBINARY(20) NOT NULL + ID BIGINT AUTO_INCREMENT PRIMARY KEY, + BINARY_DATA VARBINARY(20) NOT NULL ); CREATE TABLE CHAIN4 ( - FOUR BIGINT AUTO_INCREMENT PRIMARY KEY, - FOUR_VALUE VARCHAR(20) + FOUR BIGINT AUTO_INCREMENT PRIMARY KEY, + FOUR_VALUE VARCHAR(20) ); CREATE TABLE CHAIN3 ( - THREE BIGINT AUTO_INCREMENT PRIMARY KEY, - THREE_VALUE VARCHAR(20), - CHAIN4 BIGINT, - FOREIGN KEY (CHAIN4) REFERENCES CHAIN4(FOUR) + THREE BIGINT AUTO_INCREMENT PRIMARY KEY, + THREE_VALUE VARCHAR(20), + CHAIN4 BIGINT, + FOREIGN KEY (CHAIN4) REFERENCES CHAIN4 (FOUR) ); CREATE TABLE CHAIN2 ( - TWO BIGINT AUTO_INCREMENT PRIMARY KEY, - TWO_VALUE VARCHAR(20), - CHAIN3 BIGINT, - FOREIGN KEY (CHAIN3) REFERENCES CHAIN3(THREE) + TWO BIGINT AUTO_INCREMENT PRIMARY KEY, + TWO_VALUE VARCHAR(20), + CHAIN3 BIGINT, + FOREIGN KEY (CHAIN3) REFERENCES CHAIN3 (THREE) ); CREATE TABLE CHAIN1 ( - ONE BIGINT AUTO_INCREMENT PRIMARY KEY, - ONE_VALUE VARCHAR(20), - CHAIN2 BIGINT, - FOREIGN KEY (CHAIN2) REFERENCES CHAIN2(TWO) + ONE BIGINT AUTO_INCREMENT PRIMARY KEY, + ONE_VALUE VARCHAR(20), + CHAIN2 BIGINT, + FOREIGN KEY (CHAIN2) REFERENCES CHAIN2 (TWO) ); CREATE TABLE CHAIN0 ( - ZERO BIGINT AUTO_INCREMENT PRIMARY KEY, - ZERO_VALUE VARCHAR(20), - CHAIN1 BIGINT, - FOREIGN KEY (CHAIN1) REFERENCES CHAIN1(ONE) + ZERO BIGINT AUTO_INCREMENT PRIMARY KEY, + ZERO_VALUE VARCHAR(20), + CHAIN1 BIGINT, + FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE) ); CREATE TABLE NO_ID_CHAIN4 ( - FOUR BIGINT AUTO_INCREMENT PRIMARY KEY, - FOUR_VALUE VARCHAR(20) + FOUR BIGINT AUTO_INCREMENT PRIMARY KEY, + FOUR_VALUE VARCHAR(20) ); CREATE TABLE NO_ID_CHAIN3 ( - THREE_VALUE VARCHAR(20), - NO_ID_CHAIN4 BIGINT, - FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) + THREE_VALUE VARCHAR(20), + NO_ID_CHAIN4 BIGINT, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_CHAIN2 ( - TWO_VALUE VARCHAR(20), - NO_ID_CHAIN4 BIGINT, - FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) + TWO_VALUE VARCHAR(20), + NO_ID_CHAIN4 BIGINT, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_CHAIN1 ( - ONE_VALUE VARCHAR(20), - NO_ID_CHAIN4 BIGINT, - FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) + ONE_VALUE VARCHAR(20), + NO_ID_CHAIN4 BIGINT, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_CHAIN0 ( - ZERO_VALUE VARCHAR(20), - NO_ID_CHAIN4 BIGINT, - FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) + ZERO_VALUE VARCHAR(20), + NO_ID_CHAIN4 BIGINT, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_LIST_CHAIN4 ( - FOUR BIGINT AUTO_INCREMENT PRIMARY KEY, - FOUR_VALUE VARCHAR(20) + FOUR BIGINT AUTO_INCREMENT PRIMARY KEY, + FOUR_VALUE VARCHAR(20) ); CREATE TABLE NO_ID_LIST_CHAIN3 ( - THREE_VALUE VARCHAR(20), - NO_ID_LIST_CHAIN4 BIGINT, - NO_ID_LIST_CHAIN4_KEY BIGINT, - PRIMARY KEY (NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY), - FOREIGN KEY (NO_ID_LIST_CHAIN4) REFERENCES NO_ID_LIST_CHAIN4 (FOUR) + THREE_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 BIGINT, + NO_ID_LIST_CHAIN4_KEY BIGINT, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY), + FOREIGN KEY (NO_ID_LIST_CHAIN4) REFERENCES NO_ID_LIST_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_LIST_CHAIN2 ( - TWO_VALUE VARCHAR(20), - NO_ID_LIST_CHAIN4 BIGINT, - NO_ID_LIST_CHAIN4_KEY BIGINT, - NO_ID_LIST_CHAIN3_KEY BIGINT, - PRIMARY KEY (NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY), - FOREIGN KEY ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY - ) REFERENCES NO_ID_LIST_CHAIN3 ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY - ) + TWO_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 BIGINT, + NO_ID_LIST_CHAIN4_KEY BIGINT, + NO_ID_LIST_CHAIN3_KEY BIGINT, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY), + FOREIGN KEY ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY + ) REFERENCES NO_ID_LIST_CHAIN3 ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY + ) ); CREATE TABLE NO_ID_LIST_CHAIN1 ( - ONE_VALUE VARCHAR(20), - NO_ID_LIST_CHAIN4 BIGINT, - NO_ID_LIST_CHAIN4_KEY BIGINT, - NO_ID_LIST_CHAIN3_KEY BIGINT, - NO_ID_LIST_CHAIN2_KEY BIGINT, - PRIMARY KEY (NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY, - NO_ID_LIST_CHAIN2_KEY), - FOREIGN KEY ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY - ) REFERENCES NO_ID_LIST_CHAIN2 ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY - ) + ONE_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 BIGINT, + NO_ID_LIST_CHAIN4_KEY BIGINT, + NO_ID_LIST_CHAIN3_KEY BIGINT, + NO_ID_LIST_CHAIN2_KEY BIGINT, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY), + FOREIGN KEY ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY + ) REFERENCES NO_ID_LIST_CHAIN2 ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY + ) ); CREATE TABLE NO_ID_LIST_CHAIN0 ( - ZERO_VALUE VARCHAR(20), - NO_ID_LIST_CHAIN4 BIGINT, - NO_ID_LIST_CHAIN4_KEY BIGINT, - NO_ID_LIST_CHAIN3_KEY BIGINT, - NO_ID_LIST_CHAIN2_KEY BIGINT, - NO_ID_LIST_CHAIN1_KEY BIGINT, - PRIMARY KEY (NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY, - NO_ID_LIST_CHAIN2_KEY, - NO_ID_LIST_CHAIN1_KEY), - FOREIGN KEY ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY, - NO_ID_LIST_CHAIN2_KEY - ) REFERENCES NO_ID_LIST_CHAIN1 ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY, - NO_ID_LIST_CHAIN2_KEY - ) + ZERO_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 BIGINT, + NO_ID_LIST_CHAIN4_KEY BIGINT, + NO_ID_LIST_CHAIN3_KEY BIGINT, + NO_ID_LIST_CHAIN2_KEY BIGINT, + NO_ID_LIST_CHAIN1_KEY BIGINT, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY, + NO_ID_LIST_CHAIN1_KEY), + FOREIGN KEY ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY + ) REFERENCES NO_ID_LIST_CHAIN1 ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY + ) ); - CREATE TABLE NO_ID_MAP_CHAIN4 ( - FOUR BIGINT AUTO_INCREMENT PRIMARY KEY, - FOUR_VALUE VARCHAR(20) + FOUR BIGINT AUTO_INCREMENT PRIMARY KEY, + FOUR_VALUE VARCHAR(20) ); CREATE TABLE NO_ID_MAP_CHAIN3 ( - THREE_VALUE VARCHAR(20), - NO_ID_MAP_CHAIN4 BIGINT, - NO_ID_MAP_CHAIN4_KEY VARCHAR(20), - PRIMARY KEY (NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY), - FOREIGN KEY (NO_ID_MAP_CHAIN4) REFERENCES NO_ID_MAP_CHAIN4 (FOUR) + THREE_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 BIGINT, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY), + FOREIGN KEY (NO_ID_MAP_CHAIN4) REFERENCES NO_ID_MAP_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_MAP_CHAIN2 ( - TWO_VALUE VARCHAR(20), - NO_ID_MAP_CHAIN4 BIGINT, - NO_ID_MAP_CHAIN4_KEY VARCHAR(20), - NO_ID_MAP_CHAIN3_KEY VARCHAR(20), - PRIMARY KEY (NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY), - FOREIGN KEY ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY - ) REFERENCES NO_ID_MAP_CHAIN3 ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY - ) + TWO_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 BIGINT, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + NO_ID_MAP_CHAIN3_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY), + FOREIGN KEY ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY + ) REFERENCES NO_ID_MAP_CHAIN3 ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY + ) ); CREATE TABLE NO_ID_MAP_CHAIN1 ( - ONE_VALUE VARCHAR(20), - NO_ID_MAP_CHAIN4 BIGINT, - NO_ID_MAP_CHAIN4_KEY VARCHAR(20), - NO_ID_MAP_CHAIN3_KEY VARCHAR(20), - NO_ID_MAP_CHAIN2_KEY VARCHAR(20), - PRIMARY KEY (NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY, - NO_ID_MAP_CHAIN2_KEY), - FOREIGN KEY ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY - ) REFERENCES NO_ID_MAP_CHAIN2 ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY - ) + ONE_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 BIGINT, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + NO_ID_MAP_CHAIN3_KEY VARCHAR(20), + NO_ID_MAP_CHAIN2_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY), + FOREIGN KEY ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY + ) REFERENCES NO_ID_MAP_CHAIN2 ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY + ) ); CREATE TABLE NO_ID_MAP_CHAIN0 ( - ZERO_VALUE VARCHAR(20), - NO_ID_MAP_CHAIN4 BIGINT, - NO_ID_MAP_CHAIN4_KEY VARCHAR(20), - NO_ID_MAP_CHAIN3_KEY VARCHAR(20), - NO_ID_MAP_CHAIN2_KEY VARCHAR(20), - NO_ID_MAP_CHAIN1_KEY VARCHAR(20), - PRIMARY KEY (NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY, - NO_ID_MAP_CHAIN2_KEY, - NO_ID_MAP_CHAIN1_KEY), - FOREIGN KEY ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY, - NO_ID_MAP_CHAIN2_KEY - ) REFERENCES NO_ID_MAP_CHAIN1 ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY, - NO_ID_MAP_CHAIN2_KEY - ) + ZERO_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 BIGINT, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + NO_ID_MAP_CHAIN3_KEY VARCHAR(20), + NO_ID_MAP_CHAIN2_KEY VARCHAR(20), + NO_ID_MAP_CHAIN1_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY, + NO_ID_MAP_CHAIN1_KEY), + FOREIGN KEY ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY + ) REFERENCES NO_ID_MAP_CHAIN1 ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY + ) ); CREATE TABLE VERSIONED_AGGREGATE ( - ID BIGINT AUTO_INCREMENT PRIMARY KEY, - VERSION BIGINT + ID BIGINT AUTO_INCREMENT PRIMARY KEY, + VERSION BIGINT ); +CREATE TABLE WITH_READ_ONLY +( + ID BIGINT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(200), + READ_ONLY VARCHAR(200) DEFAULT 'from-db' +); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql index 9ac303a0ed..3e9396f743 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql @@ -11,302 +11,309 @@ DROP TABLE CHAIN3; DROP TABLE CHAIN2; DROP TABLE CHAIN1; DROP TABLE CHAIN0; +DROP TABLE WITH_READ_ONLY; CREATE TABLE LEGO_SET ( - id1 SERIAL PRIMARY KEY, - NAME VARCHAR(30) + id1 SERIAL PRIMARY KEY, + NAME VARCHAR(30) ); CREATE TABLE MANUAL ( - id2 SERIAL PRIMARY KEY, - LEGO_SET BIGINT, - ALTERNATIVE BIGINT, - CONTENT VARCHAR(2000) + id2 SERIAL PRIMARY KEY, + LEGO_SET BIGINT, + ALTERNATIVE BIGINT, + CONTENT VARCHAR(2000) ); ALTER TABLE MANUAL - ADD FOREIGN KEY (LEGO_SET) - REFERENCES LEGO_SET (id1); + ADD FOREIGN KEY (LEGO_SET) + REFERENCES LEGO_SET (id1); CREATE TABLE ONE_TO_ONE_PARENT ( - id3 SERIAL PRIMARY KEY, - content VARCHAR(30) + id3 SERIAL PRIMARY KEY, + content VARCHAR(30) ); CREATE TABLE Child_No_Id ( - ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, - content VARCHAR(30) + ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, + content VARCHAR(30) ); CREATE TABLE LIST_PARENT ( - id4 SERIAL PRIMARY KEY, - NAME VARCHAR(100) + id4 SERIAL PRIMARY KEY, + NAME VARCHAR(100) ); CREATE TABLE element_no_id ( - content VARCHAR(100), - LIST_PARENT_key BIGINT, - LIST_PARENT INTEGER + content VARCHAR(100), + LIST_PARENT_key BIGINT, + LIST_PARENT INTEGER ); CREATE TABLE ARRAY_OWNER ( - ID SERIAL PRIMARY KEY, - DIGITS VARCHAR(20)[10], - MULTIDIMENSIONAL VARCHAR(20)[10][10] + ID SERIAL PRIMARY KEY, + DIGITS VARCHAR(20)[10], + MULTIDIMENSIONAL VARCHAR(20)[10][10] ); CREATE TABLE BYTE_ARRAY_OWNER ( - ID SERIAL PRIMARY KEY, - BINARY_DATA BYTEA NOT NULL + ID SERIAL PRIMARY KEY, + BINARY_DATA BYTEA NOT NULL ); CREATE TABLE CHAIN4 ( - FOUR SERIAL PRIMARY KEY, - FOUR_VALUE VARCHAR(20) + FOUR SERIAL PRIMARY KEY, + FOUR_VALUE VARCHAR(20) ); CREATE TABLE CHAIN3 ( - THREE SERIAL PRIMARY KEY, - THREE_VALUE VARCHAR(20), - CHAIN4 BIGINT, - FOREIGN KEY (CHAIN4) REFERENCES CHAIN4 (FOUR) + THREE SERIAL PRIMARY KEY, + THREE_VALUE VARCHAR(20), + CHAIN4 BIGINT, + FOREIGN KEY (CHAIN4) REFERENCES CHAIN4 (FOUR) ); CREATE TABLE CHAIN2 ( - TWO SERIAL PRIMARY KEY, - TWO_VALUE VARCHAR(20), - CHAIN3 BIGINT, - FOREIGN KEY (CHAIN3) REFERENCES CHAIN3 (THREE) + TWO SERIAL PRIMARY KEY, + TWO_VALUE VARCHAR(20), + CHAIN3 BIGINT, + FOREIGN KEY (CHAIN3) REFERENCES CHAIN3 (THREE) ); CREATE TABLE CHAIN1 ( - ONE SERIAL PRIMARY KEY, - ONE_VALUE VARCHAR(20), - CHAIN2 BIGINT, - FOREIGN KEY (CHAIN2) REFERENCES CHAIN2 (TWO) + ONE SERIAL PRIMARY KEY, + ONE_VALUE VARCHAR(20), + CHAIN2 BIGINT, + FOREIGN KEY (CHAIN2) REFERENCES CHAIN2 (TWO) ); CREATE TABLE CHAIN0 ( - ZERO SERIAL PRIMARY KEY, - ZERO_VALUE VARCHAR(20), - CHAIN1 BIGINT, - FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE) + ZERO SERIAL PRIMARY KEY, + ZERO_VALUE VARCHAR(20), + CHAIN1 BIGINT, + FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE) ); CREATE TABLE NO_ID_CHAIN4 ( - FOUR SERIAL PRIMARY KEY, - FOUR_VALUE VARCHAR(20) + FOUR SERIAL PRIMARY KEY, + FOUR_VALUE VARCHAR(20) ); CREATE TABLE NO_ID_CHAIN3 ( - THREE_VALUE VARCHAR(20), - NO_ID_CHAIN4 BIGINT, - FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) + THREE_VALUE VARCHAR(20), + NO_ID_CHAIN4 BIGINT, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_CHAIN2 ( - TWO_VALUE VARCHAR(20), - NO_ID_CHAIN4 BIGINT, - FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) + TWO_VALUE VARCHAR(20), + NO_ID_CHAIN4 BIGINT, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_CHAIN1 ( - ONE_VALUE VARCHAR(20), - NO_ID_CHAIN4 BIGINT, - FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) + ONE_VALUE VARCHAR(20), + NO_ID_CHAIN4 BIGINT, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_CHAIN0 ( - ZERO_VALUE VARCHAR(20), - NO_ID_CHAIN4 BIGINT, - FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) + ZERO_VALUE VARCHAR(20), + NO_ID_CHAIN4 BIGINT, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_LIST_CHAIN4 ( - FOUR SERIAL PRIMARY KEY, - FOUR_VALUE VARCHAR(20) + FOUR SERIAL PRIMARY KEY, + FOUR_VALUE VARCHAR(20) ); CREATE TABLE NO_ID_LIST_CHAIN3 ( - THREE_VALUE VARCHAR(20), - NO_ID_LIST_CHAIN4 BIGINT, - NO_ID_LIST_CHAIN4_KEY BIGINT, - PRIMARY KEY (NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY), - FOREIGN KEY (NO_ID_LIST_CHAIN4) REFERENCES NO_ID_LIST_CHAIN4 (FOUR) + THREE_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 BIGINT, + NO_ID_LIST_CHAIN4_KEY BIGINT, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY), + FOREIGN KEY (NO_ID_LIST_CHAIN4) REFERENCES NO_ID_LIST_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_LIST_CHAIN2 ( - TWO_VALUE VARCHAR(20), - NO_ID_LIST_CHAIN4 BIGINT, - NO_ID_LIST_CHAIN4_KEY BIGINT, - NO_ID_LIST_CHAIN3_KEY BIGINT, - PRIMARY KEY (NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY), - FOREIGN KEY ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY - ) REFERENCES NO_ID_LIST_CHAIN3 ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY - ) + TWO_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 BIGINT, + NO_ID_LIST_CHAIN4_KEY BIGINT, + NO_ID_LIST_CHAIN3_KEY BIGINT, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY), + FOREIGN KEY ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY + ) REFERENCES NO_ID_LIST_CHAIN3 ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY + ) ); CREATE TABLE NO_ID_LIST_CHAIN1 ( - ONE_VALUE VARCHAR(20), - NO_ID_LIST_CHAIN4 BIGINT, - NO_ID_LIST_CHAIN4_KEY BIGINT, - NO_ID_LIST_CHAIN3_KEY BIGINT, - NO_ID_LIST_CHAIN2_KEY BIGINT, - PRIMARY KEY (NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY, - NO_ID_LIST_CHAIN2_KEY), - FOREIGN KEY ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY - ) REFERENCES NO_ID_LIST_CHAIN2 ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY - ) + ONE_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 BIGINT, + NO_ID_LIST_CHAIN4_KEY BIGINT, + NO_ID_LIST_CHAIN3_KEY BIGINT, + NO_ID_LIST_CHAIN2_KEY BIGINT, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY), + FOREIGN KEY ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY + ) REFERENCES NO_ID_LIST_CHAIN2 ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY + ) ); CREATE TABLE NO_ID_LIST_CHAIN0 ( - ZERO_VALUE VARCHAR(20), - NO_ID_LIST_CHAIN4 BIGINT, - NO_ID_LIST_CHAIN4_KEY BIGINT, - NO_ID_LIST_CHAIN3_KEY BIGINT, - NO_ID_LIST_CHAIN2_KEY BIGINT, - NO_ID_LIST_CHAIN1_KEY BIGINT, - PRIMARY KEY (NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY, - NO_ID_LIST_CHAIN2_KEY, - NO_ID_LIST_CHAIN1_KEY), - FOREIGN KEY ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY, - NO_ID_LIST_CHAIN2_KEY - ) REFERENCES NO_ID_LIST_CHAIN1 ( - NO_ID_LIST_CHAIN4, - NO_ID_LIST_CHAIN4_KEY, - NO_ID_LIST_CHAIN3_KEY, - NO_ID_LIST_CHAIN2_KEY - ) + ZERO_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 BIGINT, + NO_ID_LIST_CHAIN4_KEY BIGINT, + NO_ID_LIST_CHAIN3_KEY BIGINT, + NO_ID_LIST_CHAIN2_KEY BIGINT, + NO_ID_LIST_CHAIN1_KEY BIGINT, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY, + NO_ID_LIST_CHAIN1_KEY), + FOREIGN KEY ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY + ) REFERENCES NO_ID_LIST_CHAIN1 ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY + ) ); - CREATE TABLE NO_ID_MAP_CHAIN4 ( - FOUR SERIAL PRIMARY KEY, - FOUR_VALUE VARCHAR(20) + FOUR SERIAL PRIMARY KEY, + FOUR_VALUE VARCHAR(20) ); CREATE TABLE NO_ID_MAP_CHAIN3 ( - THREE_VALUE VARCHAR(20), - NO_ID_MAP_CHAIN4 BIGINT, - NO_ID_MAP_CHAIN4_KEY VARCHAR(20), - PRIMARY KEY (NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY), - FOREIGN KEY (NO_ID_MAP_CHAIN4) REFERENCES NO_ID_MAP_CHAIN4 (FOUR) + THREE_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 BIGINT, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY), + FOREIGN KEY (NO_ID_MAP_CHAIN4) REFERENCES NO_ID_MAP_CHAIN4 (FOUR) ); CREATE TABLE NO_ID_MAP_CHAIN2 ( - TWO_VALUE VARCHAR(20), - NO_ID_MAP_CHAIN4 BIGINT, - NO_ID_MAP_CHAIN4_KEY VARCHAR(20), - NO_ID_MAP_CHAIN3_KEY VARCHAR(20), - PRIMARY KEY (NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY), - FOREIGN KEY ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY - ) REFERENCES NO_ID_MAP_CHAIN3 ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY - ) + TWO_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 BIGINT, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + NO_ID_MAP_CHAIN3_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY), + FOREIGN KEY ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY + ) REFERENCES NO_ID_MAP_CHAIN3 ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY + ) ); CREATE TABLE NO_ID_MAP_CHAIN1 ( - ONE_VALUE VARCHAR(20), - NO_ID_MAP_CHAIN4 BIGINT, - NO_ID_MAP_CHAIN4_KEY VARCHAR(20), - NO_ID_MAP_CHAIN3_KEY VARCHAR(20), - NO_ID_MAP_CHAIN2_KEY VARCHAR(20), - PRIMARY KEY (NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY, - NO_ID_MAP_CHAIN2_KEY), - FOREIGN KEY ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY - ) REFERENCES NO_ID_MAP_CHAIN2 ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY - ) + ONE_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 BIGINT, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + NO_ID_MAP_CHAIN3_KEY VARCHAR(20), + NO_ID_MAP_CHAIN2_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY), + FOREIGN KEY ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY + ) REFERENCES NO_ID_MAP_CHAIN2 ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY + ) ); CREATE TABLE NO_ID_MAP_CHAIN0 ( - ZERO_VALUE VARCHAR(20), - NO_ID_MAP_CHAIN4 BIGINT, - NO_ID_MAP_CHAIN4_KEY VARCHAR(20), - NO_ID_MAP_CHAIN3_KEY VARCHAR(20), - NO_ID_MAP_CHAIN2_KEY VARCHAR(20), - NO_ID_MAP_CHAIN1_KEY VARCHAR(20), - PRIMARY KEY (NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY, - NO_ID_MAP_CHAIN2_KEY, - NO_ID_MAP_CHAIN1_KEY), - FOREIGN KEY ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY, - NO_ID_MAP_CHAIN2_KEY - ) REFERENCES NO_ID_MAP_CHAIN1 ( - NO_ID_MAP_CHAIN4, - NO_ID_MAP_CHAIN4_KEY, - NO_ID_MAP_CHAIN3_KEY, - NO_ID_MAP_CHAIN2_KEY - ) + ZERO_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 BIGINT, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + NO_ID_MAP_CHAIN3_KEY VARCHAR(20), + NO_ID_MAP_CHAIN2_KEY VARCHAR(20), + NO_ID_MAP_CHAIN1_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY, + NO_ID_MAP_CHAIN1_KEY), + FOREIGN KEY ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY + ) REFERENCES NO_ID_MAP_CHAIN1 ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY + ) ); CREATE TABLE VERSIONED_AGGREGATE ( - ID SERIAL PRIMARY KEY, - VERSION BIGINT + ID SERIAL PRIMARY KEY, + VERSION BIGINT +); + +CREATE TABLE WITH_READ_ONLY +( + ID SERIAL PRIMARY KEY, + NAME VARCHAR(200), + READ_ONLY VARCHAR(200) DEFAULT 'from-db' ); \ No newline at end of file diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 1751c4244e..206a8c2628 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -5,7 +5,7 @@ 4.0.0 spring-data-relational - 1.2.0.BUILD-SNAPSHOT + 1.2.0.DATAJDBC-386-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -13,7 +13,7 @@ org.springframework.data spring-data-relational-parent - 1.2.0.BUILD-SNAPSHOT + 1.2.0.DATAJDBC-386-SNAPSHOT diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java index 6c8008feb1..733ad7b62f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java @@ -16,6 +16,8 @@ package org.springframework.data.relational.core.dialect; import org.springframework.data.relational.core.sql.render.SelectRenderContext; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.SqlIdentifier; /** * Represents a dialect that is implemented by a particular database. Please note that not all features are supported by @@ -50,4 +52,14 @@ default ArrayColumns getArraySupport() { * @return the {@link SelectRenderContext}. */ SelectRenderContext getSelectContext(); + + /** + * Returns the {@link IdentifierProcessing} used for processing {@link SqlIdentifier} when converting them to SQL snippets or parameter names. + * + * @return the {@link IdentifierProcessing}. Guaranteed to be not {@literal null}. + * @since 2.0 + */ + default IdentifierProcessing getIdentifierProcessing() { + return IdentifierProcessing.ANSI; + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/HsqlDbDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/HsqlDbDialect.java new file mode 100644 index 0000000000..94e2184b12 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/HsqlDbDialect.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.dialect; + +/** + * A {@link Dialect} for HsqlDb. + * + * @author Jens Schauder + */ +public class HsqlDbDialect extends AbstractDialect { + + public static final HsqlDbDialect INSTANCE = new HsqlDbDialect(); + + protected HsqlDbDialect() { } + + @Override + public LimitClause limit() { + return LIMIT_CLAUSE; + } + + private static final LimitClause LIMIT_CLAUSE = new LimitClause() { + + @Override + public String getLimit(long limit) { + return "LIMIT " + limit; + } + + @Override + public String getOffset(long offset) { + return "OFFSET " + offset; + } + + @Override + public String getLimitOffset(long limit, long offset) { + return getOffset(offset) + " " + getLimit(limit); + } + + @Override + public Position getClausePosition() { + return Position.AFTER_ORDER_BY; + } + }; + +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java new file mode 100644 index 0000000000..dd42bb2eda --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.dialect; + +/** + * The {@link Dialect} to be used with MariaDb. Since we haven't encountered any significant differences between MariaDb + * and Mysql its instance is actually {@link MySqlDialect#INSTANCE}, although that might change at any time. + * + * @author Jens Schauder + */ +public class MariaDbDialect extends MySqlDialect{ + + public static final Dialect INSTANCE = MySqlDialect.INSTANCE; + + protected MariaDbDialect() { } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java index 09c364545a..38303ed977 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java @@ -15,8 +15,13 @@ */ package org.springframework.data.relational.core.dialect; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.IdentifierProcessing.DefaultIdentifierProcessing; +import org.springframework.data.relational.domain.IdentifierProcessing.LetterCasing; +import org.springframework.data.relational.domain.IdentifierProcessing.Quoting; + /** - * An SQL dialect for MySQL. + * A SQL dialect for MySQL. * * @author Mark Paluch * @author Jens Schauder @@ -29,6 +34,8 @@ public class MySqlDialect extends AbstractDialect { */ public static final MySqlDialect INSTANCE = new MySqlDialect(); + protected MySqlDialect() { } + private static final LimitClause LIMIT_CLAUSE = new LimitClause() { /* @@ -80,4 +87,9 @@ public Position getClausePosition() { public LimitClause limit() { return LIMIT_CLAUSE; } + + @Override + public IdentifierProcessing getIdentifierProcessing() { + return new DefaultIdentifierProcessing(new Quoting("`"), LetterCasing.LOWER_CASE); + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java index 6e47b13d9f..4109775b66 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java @@ -17,6 +17,10 @@ import lombok.RequiredArgsConstructor; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.IdentifierProcessing.DefaultIdentifierProcessing; +import org.springframework.data.relational.domain.IdentifierProcessing.LetterCasing; +import org.springframework.data.relational.domain.IdentifierProcessing.Quoting; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -33,6 +37,8 @@ public class PostgresDialect extends AbstractDialect { */ public static final PostgresDialect INSTANCE = new PostgresDialect(); + protected PostgresDialect() {} + private static final LimitClause LIMIT_CLAUSE = new LimitClause() { /* @@ -116,4 +122,9 @@ public Class getArrayType(Class userType) { return ClassUtils.resolvePrimitiveIfNecessary(userType); } } + + @Override + public IdentifierProcessing getIdentifierProcessing() { + return new DefaultIdentifierProcessing(Quoting.ANSI, LetterCasing.LOWER_CASE); + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java index 73a0c2e05f..27971335bb 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java @@ -31,6 +31,8 @@ public class SqlServerDialect extends AbstractDialect { */ public static final SqlServerDialect INSTANCE = new SqlServerDialect(); + protected SqlServerDialect() { } + private static final LimitClause LIMIT_CLAUSE = new LimitClause() { /* diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java index 3008623355..df96a201e0 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java @@ -30,6 +30,8 @@ import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; +import org.springframework.data.relational.domain.SqlIdentifier; +import org.springframework.data.relational.domain.SqlIdentifier.SimpleSqlIdentifier; import org.springframework.data.util.Lazy; import org.springframework.data.util.Optionals; import org.springframework.lang.Nullable; @@ -58,9 +60,9 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent } private final RelationalMappingContext context; - private final Lazy columnName; - private final Lazy> collectionIdColumnName; - private final Lazy collectionKeyColumnName; + private final Lazy columnName; + private final Lazy> collectionIdColumnName; + private final Lazy collectionKeyColumnName; private final Lazy isEmbedded; private final Lazy embeddedPrefix; private final Lazy> columnType = Lazy.of(this::doGetColumnType); @@ -91,17 +93,24 @@ public BasicRelationalPersistentProperty(Property property, PersistentEntity Optional.ofNullable(findAnnotation(Column.class)) // .map(Column::value) // .filter(StringUtils::hasText) // + .map(name -> SqlIdentifier.quoted(name).withAdjustableLetterCasing()) // .orElseGet(() -> context.getNamingStrategy().getColumnName(this))); this.collectionIdColumnName = Lazy.of(() -> Optionals - .toStream(Optional.ofNullable(findAnnotation(MappedCollection.class)).map(MappedCollection::idColumn), - Optional.ofNullable(findAnnotation(Column.class)).map(Column::value)) // - .filter(StringUtils::hasText).findFirst()); + .toStream(Optional.ofNullable(findAnnotation(MappedCollection.class)) // + .map(MappedCollection::idColumn), // + Optional.ofNullable(findAnnotation(Column.class)) // + .map(Column::value)) // + .filter(StringUtils::hasText) // + .findFirst() // + .map(name -> SqlIdentifier.quoted(name).withAdjustableLetterCasing())); // - this.collectionKeyColumnName = Lazy.of(() -> Optionals - .toStream(Optional.ofNullable(findAnnotation(MappedCollection.class)).map(MappedCollection::keyColumn), + this.collectionKeyColumnName = Lazy.of(() -> Optionals // + .toStream(Optional.ofNullable(findAnnotation(MappedCollection.class)).map(MappedCollection::keyColumn), // Optional.ofNullable(findAnnotation(Column.class)).map(Column::keyColumn)) // - .filter(StringUtils::hasText).findFirst().orElseGet(() -> context.getNamingStrategy().getKeyColumn(this))); + .filter(StringUtils::hasText).findFirst() // + .map(name -> (SqlIdentifier) SqlIdentifier.quoted(name).withAdjustableLetterCasing()) // + .orElseGet(() -> context.getNamingStrategy().getKeyColumn(this))); } /* @@ -128,7 +137,7 @@ public boolean isReference() { * @see org.springframework.data.jdbc.core.mapping.model.JdbcPersistentProperty#getColumnName() */ @Override - public String getColumnName() { + public SimpleSqlIdentifier getColumnName() { return columnName.get(); } @@ -178,18 +187,18 @@ public RelationalPersistentEntity getOwner() { } @Override - public String getReverseColumnName() { + public SqlIdentifier getReverseColumnName() { return collectionIdColumnName.get().orElseGet(() -> context.getNamingStrategy().getReverseColumnName(this)); } @Override - public String getReverseColumnName(PersistentPropertyPathExtension path) { + public SqlIdentifier getReverseColumnName(PersistentPropertyPathExtension path) { return collectionIdColumnName.get().orElseGet(() -> context.getNamingStrategy().getReverseColumnName(path)); } @Override - public String getKeyColumn() { + public SqlIdentifier getKeyColumn() { return isQualified() ? collectionKeyColumnName.get() : null; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java index 3ba48009b4..5edf4ac9c7 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java @@ -18,6 +18,8 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.springframework.data.relational.domain.SqlIdentifier; +import org.springframework.data.relational.domain.SqlIdentifier.SimpleSqlIdentifier; import org.springframework.data.util.Lazy; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; @@ -32,19 +34,19 @@ class CachingNamingStrategy implements NamingStrategy { private final NamingStrategy delegate; - private final Map columnNames = new ConcurrentHashMap<>(); - private final Map keyColumns = new ConcurrentHashMap<>(); - private final Map, String> qualifiedTableNames = new ConcurrentReferenceHashMap<>(); - private final Map, String> tableNames = new ConcurrentReferenceHashMap<>(); + private final Map columnNames = new ConcurrentHashMap<>(); + private final Map keyColumns = new ConcurrentHashMap<>(); + private final Map, SqlIdentifier> qualifiedTableNames = new ConcurrentReferenceHashMap<>(); + private final Map, SqlIdentifier> tableNames = new ConcurrentReferenceHashMap<>(); - private final Lazy schema; + private final Lazy schema; /** * Creates a new {@link CachingNamingStrategy} with the given delegate {@link NamingStrategy}. * * @param delegate must not be {@literal null}. */ - public CachingNamingStrategy(NamingStrategy delegate) { + CachingNamingStrategy(NamingStrategy delegate) { Assert.notNull(delegate, "Delegate must not be null!"); @@ -57,7 +59,7 @@ public CachingNamingStrategy(NamingStrategy delegate) { * @see org.springframework.data.relational.core.mapping.NamingStrategy#getKeyColumn(org.springframework.data.relational.core.mapping.RelationalPersistentProperty) */ @Override - public String getKeyColumn(RelationalPersistentProperty property) { + public SqlIdentifier getKeyColumn(RelationalPersistentProperty property) { return keyColumns.computeIfAbsent(property, delegate::getKeyColumn); } @@ -66,7 +68,7 @@ public String getKeyColumn(RelationalPersistentProperty property) { * @see org.springframework.data.relational.core.mapping.NamingStrategy#getQualifiedTableName(java.lang.Class) */ @Override - public String getQualifiedTableName(Class type) { + public SqlIdentifier getQualifiedTableName(Class type) { return qualifiedTableNames.computeIfAbsent(type, delegate::getQualifiedTableName); } @@ -75,7 +77,7 @@ public String getQualifiedTableName(Class type) { * @see org.springframework.data.relational.core.mapping.NamingStrategy#getTableName(java.lang.Class) */ @Override - public String getTableName(Class type) { + public SqlIdentifier getTableName(Class type) { return tableNames.computeIfAbsent(type, delegate::getTableName); } @@ -84,7 +86,7 @@ public String getTableName(Class type) { * @see org.springframework.data.relational.core.mapping.NamingStrategy#getReverseColumnName(org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension) */ @Override - public String getReverseColumnName(PersistentPropertyPathExtension path) { + public SqlIdentifier getReverseColumnName(PersistentPropertyPathExtension path) { return delegate.getReverseColumnName(path); } @@ -93,7 +95,7 @@ public String getReverseColumnName(PersistentPropertyPathExtension path) { * @see org.springframework.data.relational.core.mapping.NamingStrategy#getReverseColumnName(org.springframework.data.relational.core.mapping.RelationalPersistentProperty) */ @Override - public String getReverseColumnName(RelationalPersistentProperty property) { + public SqlIdentifier getReverseColumnName(RelationalPersistentProperty property) { return delegate.getReverseColumnName(property); } @@ -102,7 +104,7 @@ public String getReverseColumnName(RelationalPersistentProperty property) { * @see org.springframework.data.relational.core.mapping.NamingStrategy#getSchema() */ @Override - public String getSchema() { + public SqlIdentifier getSchema() { return schema.get(); } @@ -111,7 +113,7 @@ public String getSchema() { * @see org.springframework.data.relational.core.mapping.NamingStrategy#getColumnName(org.springframework.data.relational.core.mapping.RelationalPersistentProperty) */ @Override - public String getColumnName(RelationalPersistentProperty property) { + public SimpleSqlIdentifier getColumnName(RelationalPersistentProperty property) { return columnNames.computeIfAbsent(property, delegate::getColumnName); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/NamingStrategy.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/NamingStrategy.java index d80dce80b2..d75d575ae7 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/NamingStrategy.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/NamingStrategy.java @@ -15,6 +15,10 @@ */ package org.springframework.data.relational.core.mapping; +import static org.springframework.data.relational.domain.SqlIdentifier.*; + +import org.springframework.data.relational.domain.SqlIdentifier; +import org.springframework.data.relational.domain.SqlIdentifier.*; import org.springframework.data.util.ParsingUtils; import org.springframework.util.Assert; @@ -45,34 +49,34 @@ public interface NamingStrategy { * * @return Empty String representing no schema */ - default String getSchema() { - return ""; + default SqlIdentifier getSchema() { + return SqlIdentifier.EMPTY; } /** * The name of the table to be used for persisting entities having the type passed as an argument. The default * implementation takes the {@code type.getSimpleName()} and separates camel case parts with '_'. */ - default String getTableName(Class type) { + default SqlIdentifier getTableName(Class type) { Assert.notNull(type, "Type must not be null."); - return ParsingUtils.reconcatenateCamelCase(type.getSimpleName(), "_"); + return quoted(ParsingUtils.reconcatenateCamelCase(type.getSimpleName(), "_")).withAdjustableLetterCasing(); } /** * Defaults to return the given {@link RelationalPersistentProperty}'s name with the parts of a camel case name * separated by '_'; */ - default String getColumnName(RelationalPersistentProperty property) { + default SimpleSqlIdentifier getColumnName(RelationalPersistentProperty property) { Assert.notNull(property, "Property must not be null."); - return ParsingUtils.reconcatenateCamelCase(property.getName(), "_"); + return quoted(ParsingUtils.reconcatenateCamelCase(property.getName(), "_")).withAdjustableLetterCasing(); } - default String getQualifiedTableName(Class type) { - return this.getSchema() + (this.getSchema().equals("") ? "" : ".") + this.getTableName(type); + default SqlIdentifier getQualifiedTableName(Class type) { + return this.getSchema().concat(this.getTableName(type)); } /** @@ -81,14 +85,14 @@ default String getQualifiedTableName(Class type) { * @param property The property who's column name in the owner table is required * @return a column name. Must not be {@code null}. */ - default String getReverseColumnName(RelationalPersistentProperty property) { + default SqlIdentifier getReverseColumnName(RelationalPersistentProperty property) { Assert.notNull(property, "Property must not be null."); return property.getOwner().getTableName(); } - default String getReverseColumnName(PersistentPropertyPathExtension path) { + default SqlIdentifier getReverseColumnName(PersistentPropertyPathExtension path) { return getTableName(path.getIdDefiningParentPath().getLeafEntity().getType()); } @@ -99,10 +103,10 @@ default String getReverseColumnName(PersistentPropertyPathExtension path) { * * @return name of the key column. Must not be {@code null}. */ - default String getKeyColumn(RelationalPersistentProperty property) { + default SqlIdentifier getKeyColumn(RelationalPersistentProperty property) { Assert.notNull(property, "Property must not be null."); - return getReverseColumnName(property) + "_key"; + return getReverseColumnName(property).suffix("_key"); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java index 85f75a7df6..64fd91ac70 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java @@ -20,6 +20,8 @@ import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.relational.domain.SqlIdentifier; +import org.springframework.data.relational.domain.SqlIdentifier.SimpleSqlIdentifier; import org.springframework.data.util.Lazy; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -38,7 +40,7 @@ public class PersistentPropertyPathExtension { private final @Nullable PersistentPropertyPath path; private final MappingContext, RelationalPersistentProperty> context; - private final Lazy columnAlias = Lazy.of(() -> prefixWithTableAlias(getColumnName())); + private final Lazy columnAlias = Lazy.of(() -> prefixWithTableAlias(getColumnName())); /** * Creates the empty path referencing the root itself. @@ -156,10 +158,9 @@ public boolean isCollectionLike() { * * @throws IllegalStateException when called on an empty path. */ - public String getReverseColumnName() { + public SqlIdentifier getReverseColumnName() { Assert.state(path != null, "Empty paths don't have a reverse column name"); - return path.getRequiredLeafProperty().getReverseColumnName(this); } @@ -168,7 +169,7 @@ public String getReverseColumnName() { * * @throws IllegalStateException when called on an empty path. */ - public String getReverseColumnNameAlias() { + public SqlIdentifier getReverseColumnNameAlias() { return prefixWithTableAlias(getReverseColumnName()); } @@ -178,7 +179,7 @@ public String getReverseColumnNameAlias() { * * @throws IllegalStateException when called on an empty path. */ - public String getColumnName() { + public SqlIdentifier getColumnName() { Assert.state(path != null, "Path is null"); @@ -190,7 +191,7 @@ public String getColumnName() { * * @throws IllegalStateException when called on an empty path. */ - public String getColumnAlias() { + public SqlIdentifier getColumnAlias() { return columnAlias.get(); } @@ -228,7 +229,7 @@ public PersistentPropertyPathExtension getIdDefiningParentPath() { * * @return the name of the table. Guaranteed to be not {@literal null}. */ - public String getTableName() { + public SqlIdentifier getTableName() { return getTableOwningAncestor().getRequiredLeafEntity().getTableName(); } @@ -238,7 +239,7 @@ public String getTableName() { * @return a table alias, {@literal null} if the table owning path is the empty path. */ @Nullable - public String getTableAlias() { + public SqlIdentifier getTableAlias() { PersistentPropertyPathExtension tableOwner = getTableOwningAncestor(); @@ -249,7 +250,7 @@ public String getTableAlias() { /** * The column name of the id column of the ancestor path that represents an actual table. */ - public String getIdColumnName() { + public SqlIdentifier getIdColumnName() { return getTableOwningAncestor().getRequiredLeafEntity().getIdColumn(); } @@ -257,7 +258,7 @@ public String getIdColumnName() { * If the table owning ancestor has an id the column name of that id property is returned. Otherwise the reverse * column is returned. */ - public String getEffectiveIdColumnName() { + public SqlIdentifier getEffectiveIdColumnName() { PersistentPropertyPathExtension owner = getTableOwningAncestor(); return owner.path == null ? owner.getRequiredLeafEntity().getIdColumn() : owner.getReverseColumnName(); @@ -296,8 +297,8 @@ public RelationalPersistentProperty getRequiredIdProperty() { * @return May be {@literal null}. */ @Nullable - public String getQualifierColumn() { - return path == null ? "" : path.getRequiredLeafProperty().getKeyColumn(); + public SqlIdentifier getQualifierColumn() { + return path == null ? SqlIdentifier.EMPTY : path.getRequiredLeafProperty().getKeyColumn(); } /** @@ -384,7 +385,7 @@ private PersistentPropertyPathExtension getTableOwningAncestor() { return isEntity() && !isEmbedded() ? this : getParentPath().getTableOwningAncestor(); } - private String assembleTableAlias() { + private SqlIdentifier assembleTableAlias() { Assert.state(path != null, "Path is null"); @@ -393,15 +394,15 @@ private String assembleTableAlias() { if (path.getLength() == 1) { Assert.notNull(prefix, "Prefix mus not be null."); - return prefix; + return SqlIdentifier.quoted(prefix).withAdjustableLetterCasing(); } PersistentPropertyPathExtension parentPath = getParentPath(); - return parentPath.isEmbedded() ? parentPath.assembleTableAlias() + prefix - : parentPath.assembleTableAlias() + "_" + prefix; + return parentPath.isEmbedded() ? parentPath.assembleTableAlias().suffix(prefix) + : parentPath.assembleTableAlias().suffix("_" + prefix); } - private String assembleColumnName(String suffix) { + private SqlIdentifier assembleColumnName(SimpleSqlIdentifier suffix) { Assert.state(path != null, "Path is null"); @@ -418,17 +419,17 @@ private String assembleColumnName(String suffix) { String embeddedPrefix = parentLeaf.getEmbeddedPrefix(); - return getParentPath().assembleColumnName(embeddedPrefix + suffix); + return getParentPath().assembleColumnName(suffix.prefix(embeddedPrefix)); } private RelationalPersistentEntity getRequiredLeafEntity() { return path == null ? entity : context.getRequiredPersistentEntity(path.getRequiredLeafProperty().getActualType()); } - private String prefixWithTableAlias(String columnName) { + private SqlIdentifier prefixWithTableAlias(SqlIdentifier columnName) { - String tableAlias = getTableAlias(); - return tableAlias == null ? columnName : tableAlias + "_" + columnName; + SqlIdentifier tableAlias = getTableAlias(); + return tableAlias == null ? columnName : columnName.prefix(tableAlias, "_"); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntity.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntity.java index ddac00d80f..d354837803 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntity.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntity.java @@ -16,6 +16,7 @@ package org.springframework.data.relational.core.mapping; import org.springframework.data.mapping.model.MutablePersistentEntity; +import org.springframework.data.relational.domain.SqlIdentifier; /** * A {@link org.springframework.data.mapping.PersistentEntity} interface with additional methods for JDBC/RDBMS related @@ -31,12 +32,12 @@ public interface RelationalPersistentEntity extends MutablePersistentEntity extends BasicPersistentEntity { private final NamingStrategy namingStrategy; - private final Lazy> tableName; + private final Lazy> tableName; /** * Creates a new {@link RelationalPersistentEntityImpl} for the given {@link TypeInformation}. @@ -50,6 +51,7 @@ class RelationalPersistentEntityImpl extends BasicPersistentEntity SqlIdentifier.quoted(name).withAdjustableLetterCasing()) // ); } @@ -58,7 +60,7 @@ class RelationalPersistentEntityImpl extends BasicPersistentEntity namingStrategy.getQualifiedTableName(getType())); } @@ -67,7 +69,7 @@ public String getTableName() { * @see org.springframework.data.jdbc.core.mapping.model.JdbcPersistentEntity#getIdColumn() */ @Override - public String getIdColumn() { + public SqlIdentifier getIdColumn() { return getRequiredIdProperty().getColumnName(); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java index 0485542465..b05dcb84a9 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java @@ -16,6 +16,8 @@ package org.springframework.data.relational.core.mapping; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.relational.domain.SqlIdentifier; +import org.springframework.data.relational.domain.SqlIdentifier.SimpleSqlIdentifier; import org.springframework.lang.Nullable; /** @@ -34,7 +36,7 @@ public interface RelationalPersistentProperty extends PersistentProperty targetType) { + public static Identifier of(SqlIdentifier name, Object value, Class targetType) { - Assert.hasText(name, "Name must not be empty!"); + Assert.notNull(name, "Name must not be empty!"); Assert.notNull(targetType, "Target type must not be null!"); return new Identifier(Collections.singletonList(new SingleIdentifierValue(name, value, targetType))); @@ -83,7 +83,7 @@ public static Identifier of(String name, Object value, Class targetType) { * @param map must not be {@literal null}. * @return the {@link Identifier} from a {@link Map} of name to value tuples. */ - public static Identifier from(Map map) { + public static Identifier from(Map map) { Assert.notNull(map, "Map must not be null!"); @@ -111,9 +111,9 @@ public static Identifier from(Map map) { * @return the {@link Identifier} containing all existing keys and the key part for {@code name}, {@code value}, and a * {@link Class target type}. */ - public Identifier withPart(String name, Object value, Class targetType) { + public Identifier withPart(SqlIdentifier name, Object value, Class targetType) { - Assert.hasText(name, "Name must not be empty!"); + Assert.notNull(name, "Name must not be null!"); Assert.notNull(targetType, "Target type must not be null!"); boolean overwritten = false; @@ -141,9 +141,9 @@ public Identifier withPart(String name, Object value, Class targetType) { * * @return a {@link Map} containing the identifier name to value tuples. */ - public Map toMap() { + public Map toMap() { - Map result = new LinkedHashMap<>(); + Map result = new LinkedHashMap<>(); forEach((name, value, type) -> result.put(name, value)); return result; } @@ -188,7 +188,7 @@ public int size() { @AllArgsConstructor(access = AccessLevel.PRIVATE) static class SingleIdentifierValue { - String name; + SqlIdentifier name; Object value; Class targetType; } @@ -204,11 +204,11 @@ public interface IdentifierConsumer { /** * Performs this operation on the given arguments. - * + * * @param name * @param value * @param targetType */ - void accept(String name, Object value, Class targetType); + void accept(SqlIdentifier name, Object value, Class targetType); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/domain/IdentifierProcessing.java b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/IdentifierProcessing.java new file mode 100644 index 0000000000..78dccb89fe --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/IdentifierProcessing.java @@ -0,0 +1,151 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.domain; + +/** + * An interface describing the processing steps for the conversion of {@link SqlIdentifier} to SQL snippets or column + * names. + * + * @author Jens Schauder + * @since 2.0 + */ +public interface IdentifierProcessing { + + /** + * An {@link IdentifierProcessing} that can be used for databases adhering to the SQL standard which uses double + * quotes ({@literal "}) for quoting and makes unquoted literals equivalent to upper case. + */ + IdentifierProcessing ANSI = new DefaultIdentifierProcessing(Quoting.ANSI, LetterCasing.UPPER_CASE); + + /** + * Converts a {@link String} representing a bare name of an identifier to a {@link String} with proper quoting + * applied. + * + * @param identifier the name of an identifier. Must not be {@literal null}. + * @return a quoted name of an identifier. Guaranteed to be not {@literal null}. + */ + String quote(String identifier); + + /** + * Standardizes the use of upper and lower case letters in an identifier in such a way that semantically the same + * identifier results from the quoted and the unquoted version. If this is not possible use of + * {@link LetterCasing#AS_IS} is recommended. + * + * @param identifier an identifier with arbitrary upper and lower cases. must not be {@literal null}. + * @return an identifier with standardized use of upper and lower case letter. Guaranteed to be not {@literal null}. + */ + String standardizeLetterCase(String identifier); + + /** + * An {@link IdentifierProcessing} implementation based on two implementations for the quoting and for the letter case + * standardization. + */ + class DefaultIdentifierProcessing implements IdentifierProcessing { + + private final Quoting quoting; + private final LetterCasing letterCasing; + + public DefaultIdentifierProcessing(Quoting quoting, LetterCasing letterCasing) { + this.quoting = quoting; + this.letterCasing = letterCasing; + } + + @Override + public String quote(String identifier) { + return quoting.apply(identifier); + } + + @Override + public String standardizeLetterCase(String identifier) { + return letterCasing.apply(identifier); + } + } + + /** + * A conversion from unquoted identifiers to quoted identifiers. + * + * @author Jens Schauder + * @since 2.0 + */ + class Quoting { + + public static final Quoting ANSI = new Quoting("\""); + + private final String prefix; + private final String suffix; + + /** + * Constructs a {@literal Quoting} with potential different prefix and suffix used for quoting. + * + * @param prefix a {@literal String} prefixed before the name for quoting it. + * @param suffix a {@literal String} suffixed at the end of the name for quoting it. + */ + public Quoting(String prefix, String suffix) { + + this.prefix = prefix; + this.suffix = suffix; + } + + /** + * Constructs a {@literal Quoting} with the same {@literal String} appended in front and end of an identifier. + * + * @param quoteCharacter the value appended at the beginning and the end of a name in order to quote it. + */ + public Quoting(String quoteCharacter) { + this(quoteCharacter, quoteCharacter); + } + + public String apply(String identifier) { + return prefix + identifier + suffix; + } + } + + /** + * Encapsulates the three kinds of letter casing supported. + * + * @author Jens Schauder + * @since 2.0 + */ + enum LetterCasing { + + UPPER_CASE { + + @Override + public String apply(String identifier) { + return identifier.toUpperCase(); + } + }, + + LOWER_CASE { + + @Override + public String apply(String identifier) { + return identifier.toLowerCase(); + } + }, + + AS_IS { + + @Override + public String apply(String identifier) { + return identifier; + } + }; + + abstract String apply(String identifier); + } + +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/domain/SqlIdentifier.java b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/SqlIdentifier.java new file mode 100644 index 0000000000..d40f11cdcd --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/domain/SqlIdentifier.java @@ -0,0 +1,219 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.domain; + +import java.util.Arrays; +import java.util.Objects; +import java.util.StringJoiner; + +import org.springframework.util.Assert; + +/** + * Represents a named object that exists in the database like a table name or a column name + * + * @author Jens Schauder + * @since 2.0 + */ +public interface SqlIdentifier { + + SimpleSqlIdentifier prefix(SqlIdentifier prefix, String separator); + + SqlIdentifier suffix(String suffix); + + SqlIdentifier concat(SqlIdentifier second); + + String toSql(IdentifierProcessing processing); + + String toColumnName(IdentifierProcessing processing); + + static SimpleSqlIdentifier quoted(String name) { + return new SimpleSqlIdentifier(name, true, true); + } + + static SimpleSqlIdentifier unquoted(String name) { + return new SimpleSqlIdentifier(name, false, true); + } + + SqlIdentifier EMPTY = new SqlIdentifier() { + + @Override + public SimpleSqlIdentifier prefix(SqlIdentifier prefix, String separator) { + throw new UnsupportedOperationException("We can't prefix an empty DatabaseObjectIdentifier"); + } + + @Override + public SqlIdentifier suffix(String suffix) { + throw new UnsupportedOperationException("We can't suffix an empty DatabaseObjectIdentifier"); + } + + @Override + public SqlIdentifier concat(SqlIdentifier second) { + return second; + } + + @Override + public String toSql(IdentifierProcessing processing) { + throw new UnsupportedOperationException("An empty SqlIdentifier can't be used in to create SQL snippets"); + } + + @Override + public String toColumnName(IdentifierProcessing processing) { + throw new UnsupportedOperationException("An empty SqlIdentifier can't be used in to create column names"); + } + + public String toString() { + return ""; + } + }; + + final class SimpleSqlIdentifier implements SqlIdentifier { + + private final String name; + private final boolean quoted; + private final boolean fixedLetterCasing; + + private SimpleSqlIdentifier(String name, boolean quoted, boolean fixedLetterCasing) { + + Assert.hasText(name, "A database object must have at least on name part."); + + this.name = name; + this.quoted = quoted; + this.fixedLetterCasing = fixedLetterCasing; + } + + public SimpleSqlIdentifier withAdjustableLetterCasing() { + return new SimpleSqlIdentifier(name, quoted, false); + } + + public SimpleSqlIdentifier prefix(String prefix) { + return new SimpleSqlIdentifier(prefix + name, quoted, fixedLetterCasing); + } + + @Override + public SimpleSqlIdentifier prefix(SqlIdentifier prefix, String separator) { + + Assert.isInstanceOf(SimpleSqlIdentifier.class, prefix, "Prefixing is only supported for simple SqlIdentifier"); + + return new SimpleSqlIdentifier(((SimpleSqlIdentifier) prefix).name + separator + name, quoted, fixedLetterCasing); + } + + public SimpleSqlIdentifier suffix(String suffix) { + return new SimpleSqlIdentifier(name + suffix, quoted, fixedLetterCasing); + } + + @Override + public boolean equals(Object o) { + + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + SimpleSqlIdentifier that = (SimpleSqlIdentifier) o; + return quoted == that.quoted && Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + + int result = Objects.hash(quoted); + result = 31 * result + name.hashCode(); + return result; + } + + @Override + public String toString() { + return "DatabaseObjectIdentifier{" + "name=" + name + ", quoted=" + quoted + '}'; + } + + @Override + public SqlIdentifier concat(SqlIdentifier second) { + // TODO: this is completely broken and needs fixing. + return new CombinedSqlIdentifier(this, (SimpleSqlIdentifier) second); + } + + @Override + public String toSql(IdentifierProcessing processing) { + + return quoted ? processing.quote(toColumnName(processing)) : toColumnName(processing); + } + + @Override + public String toColumnName(IdentifierProcessing processing) { + return fixedLetterCasing ? name : processing.standardizeLetterCase(name); + } + } + + final class CombinedSqlIdentifier implements SqlIdentifier { + + private final SimpleSqlIdentifier[] parts; + + private CombinedSqlIdentifier(SimpleSqlIdentifier... parts) { + this.parts = parts; + } + + @Override + public SqlIdentifier concat(SqlIdentifier second) { + throw new UnsupportedOperationException(); + } + + @Override + public SimpleSqlIdentifier prefix(SqlIdentifier prefix, String separator) { + throw new UnsupportedOperationException(); + } + + @Override + public SqlIdentifier suffix(String suffix) { + throw new UnsupportedOperationException(); + } + + @Override + public String toSql(IdentifierProcessing processing) { + + StringJoiner stringJoiner = new StringJoiner("."); + + for (SimpleSqlIdentifier namePart : parts) { + stringJoiner.add(namePart.toSql(processing)); + } + + return stringJoiner.toString(); + } + + @Override + public String toColumnName(IdentifierProcessing processing) { + throw new UnsupportedOperationException("A CombinedSqlIdentifier can't be used as a column name"); + } + + @Override + public String toString() { + return "CombinedDatabaseObjectIdentifier{" + "parts=" + Arrays.toString(parts) + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + CombinedSqlIdentifier that = (CombinedSqlIdentifier) o; + return Arrays.equals(parts, that.parts); + } + + @Override + public int hashCode() { + return Arrays.hashCode(parts); + } + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalEntityInformation.java b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalEntityInformation.java index 0815979e1c..952f11d3ac 100755 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalEntityInformation.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalEntityInformation.java @@ -15,6 +15,7 @@ */ package org.springframework.data.relational.repository.query; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.data.repository.core.EntityInformation; /** @@ -29,5 +30,5 @@ public interface RelationalEntityInformation extends EntityInformation extends EntityMetadata { * * @return */ - String getTableName(); + SqlIdentifier getTableName(); /** * Returns the {@link RelationalPersistentEntity} that supposed to determine the table to be queried. diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/SimpleRelationalEntityMetadata.java b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/SimpleRelationalEntityMetadata.java index e2123a85e8..3887205ca7 100755 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/SimpleRelationalEntityMetadata.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/SimpleRelationalEntityMetadata.java @@ -18,6 +18,7 @@ import lombok.Getter; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.util.Assert; /** @@ -56,7 +57,7 @@ public Class getJavaType() { /* (non-Javadoc) * @see org.springframework.data.relational.repository.query.RelationalEntityMetadata#getTableName() */ - public String getTableName() { + public SqlIdentifier getTableName() { return tableEntity.getTableName(); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/support/MappingRelationalEntityInformation.java b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/support/MappingRelationalEntityInformation.java index 864dd89b3e..8fff6670b7 100755 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/support/MappingRelationalEntityInformation.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/support/MappingRelationalEntityInformation.java @@ -16,6 +16,7 @@ package org.springframework.data.relational.repository.support; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.domain.SqlIdentifier; import org.springframework.data.relational.repository.query.RelationalEntityInformation; import org.springframework.data.repository.core.support.PersistentEntityInformation; import org.springframework.lang.Nullable; @@ -32,7 +33,7 @@ public class MappingRelationalEntityInformation extends PersistentEntityI implements RelationalEntityInformation { private final RelationalPersistentEntity entityMetadata; - private final @Nullable String customTableName; + private final @Nullable SqlIdentifier customTableName; private final Class fallbackIdType; /** @@ -81,14 +82,14 @@ private MappingRelationalEntityInformation(RelationalPersistentEntity entity, super(entity); this.entityMetadata = entity; - this.customTableName = customTableName; + this.customTableName = customTableName == null ? null : SqlIdentifier.quoted(customTableName); this.fallbackIdType = idType != null ? idType : (Class) Long.class; } /* (non-Javadoc) * @see org.springframework.data.relational.repository.query.RelationalEntityInformation#getTableName() */ - public String getTableName() { + public SqlIdentifier getTableName() { return customTableName == null ? entityMetadata.getTableName() : customTableName; } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/HsqlDbDialectUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/HsqlDbDialectUnitTests.java new file mode 100644 index 0000000000..818bf406ae --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/HsqlDbDialectUnitTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.dialect; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + +/** + * Unit tests for the {@link HsqlDbDialect}. + * + * @author Jens Schauder + */ +public class HsqlDbDialectUnitTests { + + @Test // DATAJDBC-386 + public void shouldNotSupportArrays() { + + ArrayColumns arrayColumns = HsqlDbDialect.INSTANCE.getArraySupport(); + + assertThat(arrayColumns.isSupported()).isFalse(); + } + + @Test // DATAJDBC-386 + public void shouldRenderLimit() { + + LimitClause limit = HsqlDbDialect.INSTANCE.limit(); + + assertThat(limit.getClausePosition()).isEqualTo(LimitClause.Position.AFTER_ORDER_BY); + assertThat(limit.getLimit(10)).isEqualTo("LIMIT 10"); + } + + @Test // DATAJDBC-386 + public void shouldRenderOffset() { + + LimitClause limit = HsqlDbDialect.INSTANCE.limit(); + + assertThat(limit.getOffset(10)).isEqualTo("OFFSET 10"); + } + + @Test // DATAJDBC-386 + public void shouldRenderLimitOffset() { + + LimitClause limit = HsqlDbDialect.INSTANCE.limit(); + + assertThat(limit.getLimitOffset(20, 10)).isEqualTo("OFFSET 10 LIMIT 20"); + } + + @Test // DATAJDBC-386 + public void shouldQuoteIdentifiersUsingBackticks() { + + String abcQuoted = HsqlDbDialect.INSTANCE.getIdentifierProcessing().quote("abc"); + + assertThat(abcQuoted).isEqualTo("\"abc\""); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/MariaDbDialectUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/MariaDbDialectUnitTests.java new file mode 100644 index 0000000000..4a2e97053b --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/MariaDbDialectUnitTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.dialect; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + +/** + * Unit tests for {@link MariaDbDialect}. + * + * @author Jens Schauder + */ +public class MariaDbDialectUnitTests { + + @Test // DATAJDBC-278 + public void shouldNotSupportArrays() { + + ArrayColumns arrayColumns = MariaDbDialect.INSTANCE.getArraySupport(); + + assertThat(arrayColumns.isSupported()).isFalse(); + } + + @Test // DATAJDBC-278 + public void shouldRenderLimit() { + + LimitClause limit = MariaDbDialect.INSTANCE.limit(); + + assertThat(limit.getClausePosition()).isEqualTo(LimitClause.Position.AFTER_ORDER_BY); + assertThat(limit.getLimit(10)).isEqualTo("LIMIT 10"); + } + + @Test // DATAJDBC-278 + public void shouldRenderOffset() { + + LimitClause limit = MariaDbDialect.INSTANCE.limit(); + + assertThat(limit.getOffset(10)).isEqualTo("LIMIT 10, 18446744073709551615"); + } + + @Test // DATAJDBC-278 + public void shouldRenderLimitOffset() { + + LimitClause limit = MariaDbDialect.INSTANCE.limit(); + + assertThat(limit.getLimitOffset(20, 10)).isEqualTo("LIMIT 10, 20"); + } + + @Test // DATAJDBC-386 + public void shouldQuoteIdentifiersUsingBackticks() { + + String abcQuoted = MariaDbDialect.INSTANCE.getIdentifierProcessing().quote("abc"); + + assertThat(abcQuoted).isEqualTo("`abc`"); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/MySqlDialectUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/MySqlDialectUnitTests.java index 16979e44e5..f0acc45c87 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/MySqlDialectUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/dialect/MySqlDialectUnitTests.java @@ -59,4 +59,12 @@ public void shouldRenderLimitOffset() { assertThat(limit.getLimitOffset(20, 10)).isEqualTo("LIMIT 10, 20"); } + + @Test // DATAJDBC-386 + public void shouldQuoteIdentifiersUsingBackticks() { + + String abcQuoted = MySqlDialect.INSTANCE.getIdentifierProcessing().quote("abc"); + + assertThat(abcQuoted).isEqualTo("`abc`"); + } } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java index 4b7954ac8c..fa23196731 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.relational.core.mapping; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import lombok.Data; @@ -82,9 +83,9 @@ public void testTargetTypesForPropertyType() { @Test // DATAJDBC-106 public void detectsAnnotatedColumnName() { - assertThat(entity.getRequiredPersistentProperty("name").getColumnName()).isEqualTo("dummy_name"); + assertThat(entity.getRequiredPersistentProperty("name").getColumnName()).isEqualTo(quoted("dummy_name")); assertThat(entity.getRequiredPersistentProperty("localDateTime").getColumnName()) - .isEqualTo("dummy_last_updated_at"); + .isEqualTo(quoted("dummy_last_updated_at")); } @Test // DATAJDBC-218 @@ -92,8 +93,8 @@ public void detectsAnnotatedColumnAndKeyName() { RelationalPersistentProperty listProperty = entity.getRequiredPersistentProperty("someList"); - assertThat(listProperty.getReverseColumnName()).isEqualTo("dummy_column_name"); - assertThat(listProperty.getKeyColumn()).isEqualTo("dummy_key_column_name"); + assertThat(listProperty.getReverseColumnName()).isEqualTo(quoted("dummy_column_name")); + assertThat(listProperty.getKeyColumn()).isEqualTo(quoted("dummy_key_column_name")); } @Test // DATAJDBC-111 @@ -148,10 +149,10 @@ public void classificationOfCollectionLikeProperties() { .assertThat(p.isCollectionLike() && !p.isEntity()).describedAs(s + " contains either simple types or entities") .isNotEqualTo(p.isCollectionLike() && p.isEntity()); - checkEitherOr.accept(listOfString,"listOfString"); - checkEitherOr.accept(arrayOfString,"arrayOfString"); - checkEitherOr.accept(listOfEntity,"listOfEntity"); - checkEitherOr.accept(arrayOfEntity,"arrayOfEntity"); + checkEitherOr.accept(listOfString, "listOfString"); + checkEitherOr.accept(arrayOfString, "arrayOfString"); + checkEitherOr.accept(listOfEntity, "listOfEntity"); + checkEitherOr.accept(arrayOfEntity, "arrayOfEntity"); softly.assertThat(arrayOfString.getColumnType()).isEqualTo(String[].class); softly.assertThat(listOfString.getColumnType()).isEqualTo(String[].class); @@ -183,7 +184,8 @@ private static class DummyEntity { private final List listOfEntity; private final OtherEntity[] arrayOfEntity; - @MappedCollection(idColumn = "dummy_column_name", keyColumn = "dummy_key_column_name") private List someList; + @MappedCollection(idColumn = "dummy_column_name", + keyColumn = "dummy_key_column_name") private List someList; // DATACMNS-106 private @Column("dummy_name") String name; diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/NamingStrategyUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/NamingStrategyUnitTests.java index e626493291..fcb43b8961 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/NamingStrategyUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/NamingStrategyUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.relational.core.mapping; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import java.time.LocalDateTime; import java.util.List; @@ -23,6 +24,7 @@ import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.relational.core.mapping.RelationalPersistentEntityImplUnitTests.DummySubEntity; +import org.springframework.data.relational.domain.SqlIdentifier; /** * Unit tests for the {@link NamingStrategy}. @@ -40,51 +42,53 @@ public class NamingStrategyUnitTests { @Test public void getTableName() { - assertThat(target.getTableName(persistentEntity.getType())).isEqualTo("dummy_entity"); - assertThat(target.getTableName(DummySubEntity.class)).isEqualTo("dummy_sub_entity"); + assertThat(target.getTableName(persistentEntity.getType())).isEqualTo(quoted("dummy_entity")); + assertThat(target.getTableName(DummySubEntity.class)).isEqualTo(quoted("dummy_sub_entity")); } @Test public void getColumnName() { - assertThat(target.getColumnName(persistentEntity.getPersistentProperty("id"))).isEqualTo("id"); - assertThat(target.getColumnName(persistentEntity.getPersistentProperty("createdAt"))).isEqualTo("created_at"); + assertThat(target.getColumnName(persistentEntity.getPersistentProperty("id"))).isEqualTo(quoted("id")); + assertThat(target.getColumnName(persistentEntity.getPersistentProperty("createdAt"))) + .isEqualTo(quoted("created_at")); assertThat(target.getColumnName(persistentEntity.getPersistentProperty("dummySubEntities"))) - .isEqualTo("dummy_sub_entities"); + .isEqualTo(quoted("dummy_sub_entities")); } @Test public void getReverseColumnName() { assertThat(target.getReverseColumnName(persistentEntity.getPersistentProperty("dummySubEntities"))) - .isEqualTo("dummy_entity"); + .isEqualTo(quoted("dummy_entity")); } @Test public void getKeyColumn() { assertThat(target.getKeyColumn(persistentEntity.getPersistentProperty("dummySubEntities"))) - .isEqualTo("dummy_entity_key"); + .isEqualTo(quoted("dummy_entity_key")); } @Test public void getSchema() { - assertThat(target.getSchema()).isEmpty(); + assertThat(target.getSchema()).isEqualTo(SqlIdentifier.EMPTY); } @Test public void getQualifiedTableName() { - assertThat(target.getQualifiedTableName(persistentEntity.getType())).isEqualTo("dummy_entity"); + assertThat(target.getQualifiedTableName(persistentEntity.getType())).isEqualTo(quoted("dummy_entity")); NamingStrategy strategy = new NamingStrategy() { @Override - public String getSchema() { - return "schema"; + public SqlIdentifier getSchema() { + return quoted("schema"); } }; - assertThat(strategy.getQualifiedTableName(persistentEntity.getType())).isEqualTo("schema.dummy_entity"); + assertThat(strategy.getQualifiedTableName(persistentEntity.getType())) + .isEqualTo(quoted("schema").concat(quoted("dummy_entity"))); } static class DummyEntity { diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntityImplUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntityImplUnitTests.java index 07835997a9..24414c73fc 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntityImplUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntityImplUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.relational.core.mapping; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import org.junit.Test; import org.springframework.data.annotation.Id; @@ -36,7 +37,7 @@ public void discoversAnnotatedTableName() { RelationalPersistentEntity entity = mappingContext.getPersistentEntity(DummySubEntity.class); - assertThat(entity.getTableName()).isEqualTo("dummy_sub_entity"); + assertThat(entity.getTableName()).isEqualTo(quoted("dummy_sub_entity")); } @Test // DATAJDBC-294 @@ -44,7 +45,7 @@ public void considerIdColumnName() { RelationalPersistentEntity entity = mappingContext.getPersistentEntity(DummySubEntity.class); - assertThat(entity.getIdColumn()).isEqualTo("renamedId"); + assertThat(entity.getIdColumn()).isEqualTo(quoted("renamedId")); } @Test // DATAJDBC-296 @@ -52,7 +53,7 @@ public void emptyTableAnnotationFallsBackToNamingStrategy() { RelationalPersistentEntity entity = mappingContext.getPersistentEntity(DummyEntityWithEmptyAnnotation.class); - assertThat(entity.getTableName()).isEqualTo("dummy_entity_with_empty_annotation"); + assertThat(entity.getTableName()).isEqualTo(quoted("dummy_entity_with_empty_annotation")); } @Table("dummy_sub_entity") diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/SqlIdentifierUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/SqlIdentifierUnitTests.java new file mode 100644 index 0000000000..909cef124c --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/SqlIdentifierUnitTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Test; +import org.springframework.data.relational.domain.IdentifierProcessing; +import org.springframework.data.relational.domain.IdentifierProcessing.DefaultIdentifierProcessing; +import org.springframework.data.relational.domain.IdentifierProcessing.LetterCasing; +import org.springframework.data.relational.domain.IdentifierProcessing.Quoting; +import org.springframework.data.relational.domain.SqlIdentifier; + +/** + * Unit tests for SqlIdentifier. + * + * @author Jens Schauder + */ +public class SqlIdentifierUnitTests { + + public static final DefaultIdentifierProcessing BRACKETS_LOWER_CASE = new DefaultIdentifierProcessing( + new Quoting("[", "]"), LetterCasing.LOWER_CASE); + + @Test // DATAJDBC-386 + public void quotedSimpleObjectIdentifier() { + + SimpleSqlIdentifier identifier = quoted("someName"); + + assertThat(identifier.toSql(BRACKETS_LOWER_CASE)).isEqualTo("[someName]"); + assertThat(identifier.toColumnName(BRACKETS_LOWER_CASE)).isEqualTo("someName"); + + } + + @Test // DATAJDBC-386 + public void unquotedSimpleObjectIdentifier() { + + SimpleSqlIdentifier identifier = unquoted("someName"); + String sql = identifier.toSql(BRACKETS_LOWER_CASE); + + assertThat(sql).isEqualTo("someName"); + assertThat(identifier.toColumnName(BRACKETS_LOWER_CASE)).isEqualTo("someName"); + } + + @Test // DATAJDBC-386 + public void quotedSimpleObjectIdentifierWithAdjustableLetterCasing() { + + SimpleSqlIdentifier identifier = quoted("someName").withAdjustableLetterCasing(); + + assertThat(identifier.toSql(BRACKETS_LOWER_CASE)).isEqualTo("[somename]"); + assertThat(identifier.toColumnName(BRACKETS_LOWER_CASE)).isEqualTo("somename"); + + } + + @Test // DATAJDBC-386 + public void unquotedSimpleObjectIdentifierWithAdjustableLetterCasing() { + + SimpleSqlIdentifier identifier = unquoted("someName").withAdjustableLetterCasing(); + String sql = identifier.toSql(BRACKETS_LOWER_CASE); + + assertThat(sql).isEqualTo("somename"); + assertThat(identifier.toColumnName(BRACKETS_LOWER_CASE)).isEqualTo("somename"); + } + + @Test // DATAJDBC-386 + public void quotedMultipartObjectIdentifierWithAdjustableLetterCase() { + + SqlIdentifier identifier = quoted("some").withAdjustableLetterCasing() + .concat(quoted("name").withAdjustableLetterCasing()); + String sql = identifier.toSql(IdentifierProcessing.ANSI); + + assertThat(sql).isEqualTo("\"SOME\".\"NAME\""); + } + + @Test // DATAJDBC-386 + public void quotedMultipartObjectIdentifier() { + + SqlIdentifier identifier = quoted("some").concat(quoted("name")); + String sql = identifier.toSql(IdentifierProcessing.ANSI); + + assertThat(sql).isEqualTo("\"some\".\"name\""); + } + + @Test // DATAJDBC-386 + public void unquotedMultipartObjectIdentifier() { + + SqlIdentifier identifier = unquoted("some").concat(unquoted("name")); + String sql = identifier.toSql(IdentifierProcessing.ANSI); + + assertThat(sql).isEqualTo("some.name"); + } + + @Test // DATAJDBC-386 + public void equality() { + + SqlIdentifier basis = SqlIdentifier.unquoted("simple"); + SqlIdentifier equal = SqlIdentifier.unquoted("simple"); + SqlIdentifier quoted = quoted("simple"); + SqlIdentifier notSimple = SqlIdentifier.unquoted("simple").concat(unquoted("not")); + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(basis).isEqualTo(equal); + softly.assertThat(equal).isEqualTo(basis); + softly.assertThat(basis).isNotEqualTo(quoted); + softly.assertThat(basis).isNotEqualTo(notSimple); + }); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/domain/DefaultIdentifierProcessingUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/domain/DefaultIdentifierProcessingUnitTests.java new file mode 100644 index 0000000000..e153abd2ac --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/domain/DefaultIdentifierProcessingUnitTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.domain; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; +import org.springframework.data.relational.domain.IdentifierProcessing.DefaultIdentifierProcessing; +import org.springframework.data.relational.domain.IdentifierProcessing.LetterCasing; +import org.springframework.data.relational.domain.IdentifierProcessing.Quoting; + +/** + * unit tests for {@link DefaultIdentifierProcessing}. + * + * @author Jens Schauder + */ +public class DefaultIdentifierProcessingUnitTests { + + @Test // DATAJDBC-386 + public void ansiConformProcessing() { + + DefaultIdentifierProcessing processing = new DefaultIdentifierProcessing(Quoting.ANSI, LetterCasing.UPPER_CASE); + + assertThat(processing.quote("something")).isEqualTo("\"something\""); + assertThat(processing.standardizeLetterCase("aBc")).isEqualTo("ABC"); + } + + @Test // DATAJDBC-386 + public void twoCharacterAsIs() { + + DefaultIdentifierProcessing processing = new DefaultIdentifierProcessing(new Quoting("[", "]"), LetterCasing.AS_IS); + + assertThat(processing.quote("something")).isEqualTo("[something]"); + assertThat(processing.standardizeLetterCase("aBc")).isEqualTo("aBc"); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/domain/IdentifierUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/domain/IdentifierUnitTests.java index 2f74e76a40..473f08cb65 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/domain/IdentifierUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/domain/IdentifierUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.relational.domain; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.domain.SqlIdentifier.*; import java.util.ArrayList; import java.util.Collections; @@ -35,50 +36,55 @@ public class IdentifierUnitTests { @Test // DATAJDBC-326 public void getParametersByName() { - Identifier identifier = Identifier.of("aName", "aValue", String.class); + Identifier identifier = Identifier.of(unquoted("aName"), "aValue", String.class); - assertThat(identifier.toMap()).hasSize(1).containsEntry("aName", "aValue"); + assertThat(identifier.toMap()).hasSize(1).containsEntry(unquoted("aName"), "aValue"); } @Test // DATAJDBC-326 public void parametersWithStringKeysUseObjectAsTypeForNull() { - HashMap parameters = new HashMap<>(); - parameters.put("one", null); + HashMap parameters = new HashMap<>(); + parameters.put(unquoted("one"), null); Identifier identifier = Identifier.from(parameters); assertThat(identifier.getParts()) // .extracting("name", "value", "targetType") // .containsExactly( // - tuple("one", null, Object.class) // + tuple(unquoted("one"), null, Object.class) // ); } @Test // DATAJDBC-326 public void createsIdentifierFromMap() { - Identifier identifier = Identifier.from(Collections.singletonMap("aName", "aValue")); + Identifier identifier = Identifier.from(Collections.singletonMap(unquoted("aName"), "aValue")); - assertThat(identifier.toMap()).hasSize(1).containsEntry("aName", "aValue"); + assertThat(identifier.toMap()).hasSize(1).containsEntry(unquoted("aName"), "aValue"); } @Test // DATAJDBC-326 public void withAddsNewEntries() { - Identifier identifier = Identifier.from(Collections.singletonMap("aName", "aValue")).withPart("foo", "bar", - String.class); + Identifier identifier = Identifier.from(Collections.singletonMap(unquoted("aName"), "aValue")) + .withPart(unquoted("foo"), "bar", String.class); - assertThat(identifier.toMap()).hasSize(2).containsEntry("aName", "aValue").containsEntry("foo", "bar"); + assertThat(identifier.toMap()) // + .hasSize(2) // + .containsEntry(unquoted("aName"), "aValue") // + .containsEntry(unquoted("foo"), "bar"); } @Test // DATAJDBC-326 public void withOverridesExistingEntries() { - Identifier identifier = Identifier.from(Collections.singletonMap("aName", "aValue")).withPart("aName", "bar", - String.class); + Identifier identifier = Identifier.from(Collections.singletonMap(unquoted("aName"), "aValue")) + .withPart(unquoted("aName"), "bar", String.class); - assertThat(identifier.toMap()).hasSize(1).containsEntry("aName", "bar"); + assertThat(identifier.toMap()) // + .hasSize(1) // + .containsEntry(unquoted("aName"), "bar"); } @Test // DATAJDBC-326 @@ -86,7 +92,8 @@ public void forEachIteratesOverKeys() { List keys = new ArrayList<>(); - Identifier.from(Collections.singletonMap("aName", "aValue")).forEach((name, value, targetType) -> keys.add(name)); + Identifier.from(Collections.singletonMap(unquoted("aName"), "aValue")) + .forEach((name, value, targetType) -> keys.add(name.toSql(IdentifierProcessing.ANSI))); assertThat(keys).containsOnly("aName"); } @@ -94,9 +101,9 @@ public void forEachIteratesOverKeys() { @Test // DATAJDBC-326 public void equalsConsidersEquality() { - Identifier one = Identifier.from(Collections.singletonMap("aName", "aValue")); - Identifier two = Identifier.from(Collections.singletonMap("aName", "aValue")); - Identifier three = Identifier.from(Collections.singletonMap("aName", "different")); + Identifier one = Identifier.from(Collections.singletonMap(unquoted("aName"), "aValue")); + Identifier two = Identifier.from(Collections.singletonMap(unquoted("aName"), "aValue")); + Identifier three = Identifier.from(Collections.singletonMap(unquoted("aName"), "different")); assertThat(one).isEqualTo(two); assertThat(one).isNotEqualTo(three);