diff --git a/pom.xml b/pom.xml index cc97c338..f5bf0489 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-r2dbc - 1.4.0-SNAPSHOT + 1.4.0-483-read-camel-case-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC diff --git a/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java b/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java index 3401640e..506ef9b8 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java @@ -55,6 +55,7 @@ * * @author Mark Paluch * @author Louis Morgan + * @author Jens Schauder */ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStrategy { @@ -360,6 +361,11 @@ public R2dbcConverter getConverter() { return this.mappingContext; } + @Override + public String renderForGeneratedKeys(SqlIdentifier identifier) { + return dialect.renderForGeneratedKeys(identifier); + } + private RelationalPersistentEntity getRequiredPersistentEntity(Class typeToRead) { return this.mappingContext.getRequiredPersistentEntity(typeToRead); } diff --git a/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java b/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java index 24c172cd..4cd9b4f0 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java +++ b/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java @@ -88,6 +88,7 @@ * * @author Mark Paluch * @author Bogdan Ilchyshyn + * @author Jens Schauder * @since 1.1 */ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAware, ApplicationContextAware { @@ -633,7 +634,7 @@ private Mono doInsert(T entity, SqlIdentifier tableName, OutboundRow outb return statement.returnGeneratedValues(); } - return statement.returnGeneratedValues(dataAccessStrategy.toSql(identifierColumns.get(0))); + return statement.returnGeneratedValues(dataAccessStrategy.renderForGeneratedKeys(identifierColumns.get(0))); }) .map(this.dataAccessStrategy.getConverter().populateIdIfNecessary(entity)) // .all() // diff --git a/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java b/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java index 761e6448..8eda6ffc 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java @@ -29,6 +29,7 @@ import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.Parameter; import org.springframework.r2dbc.core.PreparedOperation; +import org.springframework.util.Assert; /** * Data access strategy that generalizes convenience operations using mapped entities. Typically used internally by @@ -36,6 +37,7 @@ * primary keys. * * @author Mark Paluch + * @author Jens Schauder * @see org.springframework.r2dbc.core.PreparedOperation * @deprecated since 1.2 in favor of using direct usage of {@link StatementMapper}, * {@link org.springframework.data.r2dbc.query.UpdateMapper} and {@link R2dbcConverter}. @@ -135,6 +137,19 @@ public interface ReactiveDataAccessStrategy { */ String toSql(SqlIdentifier identifier); + /** + * Render a {@link SqlIdentifier} in a way suitable for registering it as a generated key with a statement. + * + * @param identifier to render. Must not be {@literal null}. + * @return rendered identifier. Guaranteed to be not {@literal null}. + */ + default String renderForGeneratedKeys(SqlIdentifier identifier) { + + Assert.notNull(identifier, "Indentifier must not be null."); + + return identifier.toSql(IdentifierProcessing.NONE); + } + /** * Interface to retrieve parameters for named parameter processing. */ diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/H2Dialect.java b/src/main/java/org/springframework/data/r2dbc/dialect/H2Dialect.java index 0ce3f083..fad08d8b 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/H2Dialect.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/H2Dialect.java @@ -1,9 +1,12 @@ package org.springframework.data.r2dbc.dialect; +import org.springframework.data.relational.core.sql.SqlIdentifier; + /** * An SQL dialect for H2 in Postgres Compatibility mode. * * @author Mark Paluch + * @author Jens Schauder */ public class H2Dialect extends PostgresDialect { @@ -11,4 +14,9 @@ public class H2Dialect extends PostgresDialect { * Singleton instance. */ public static final H2Dialect INSTANCE = new H2Dialect(); + + @Override + public String renderForGeneratedKeys(SqlIdentifier identifier) { + return identifier.getReference(getIdentifierProcessing()); + } } diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/MySqlDialect.java b/src/main/java/org/springframework/data/r2dbc/dialect/MySqlDialect.java index fd6168e3..f71ffbce 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/MySqlDialect.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/MySqlDialect.java @@ -28,12 +28,14 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; +import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.r2dbc.core.binding.BindMarkersFactory; /** * An SQL dialect for MySQL. * * @author Mark Paluch + * @author Jens Schauder */ public class MySqlDialect extends org.springframework.data.relational.core.dialect.MySqlDialect implements R2dbcDialect { @@ -103,6 +105,11 @@ public Boolean convert(Byte s) { } } + @Override + public String renderForGeneratedKeys(SqlIdentifier identifier) { + return identifier.getReference(getIdentifierProcessing()); + } + /** * Simple singleton to convert {@link Boolean}s to their {@link Byte} representation. MySQL does not have a built-in * boolean type by default, so relies on using a byte instead. {@literal true} maps to {@code 1}. diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/R2dbcDialect.java b/src/main/java/org/springframework/data/r2dbc/dialect/R2dbcDialect.java index fe56872c..1eddac9d 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/R2dbcDialect.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/R2dbcDialect.java @@ -8,6 +8,7 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.r2dbc.mapping.R2dbcSimpleTypeHolder; import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.r2dbc.core.binding.BindMarkersFactory; /** @@ -59,4 +60,16 @@ default SimpleTypeHolder getSimpleTypeHolder() { default Collection getConverters() { return Collections.emptySet(); } + + /** + * Render a {@link SqlIdentifier} in a way suitable for registering it as a generated key with a statement. The + * default implementation renders it as it would render a SQL representation of the identifier, i.e. with quotes where + * applicable. + * + * @param identifier to render. Must not be {@literal null}. + * @return rendered identifier. Guaranteed to be not {@literal null}. + */ + default String renderForGeneratedKeys(SqlIdentifier identifier) { + return identifier.toSql(getIdentifierProcessing()); + } } diff --git a/src/main/java/org/springframework/data/r2dbc/query/QueryMapper.java b/src/main/java/org/springframework/data/r2dbc/query/QueryMapper.java index 0308af80..69f08b35 100644 --- a/src/main/java/org/springframework/data/r2dbc/query/QueryMapper.java +++ b/src/main/java/org/springframework/data/r2dbc/query/QueryMapper.java @@ -82,9 +82,10 @@ public QueryMapper(R2dbcDialect dialect, R2dbcConverter converter) { /** * Render a {@link SqlIdentifier} for SQL usage. + * The resulting String might contain quoting characters. * - * @param identifier - * @return + * @param identifier the identifier to be rendered. + * @return an identifier String. * @since 1.1 */ public String toSql(SqlIdentifier identifier) { diff --git a/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java new file mode 100644 index 00000000..5bf3777e --- /dev/null +++ b/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java @@ -0,0 +1,158 @@ +/* + * Copyright 2021 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.r2dbc.repository; + +import static org.assertj.core.api.Assertions.*; + +import io.r2dbc.spi.ConnectionFactory; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import reactor.test.StepVerifier; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.r2dbc.testing.R2dbcIntegrationTestSupport; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.lang.Nullable; + +/** + * Abstract base class for integration tests for {@link LegoSetRepository} with table and column names that contain + * upper and lower case characters. + * + * @author Jens Schauder + */ +public abstract class AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests extends R2dbcIntegrationTestSupport { + + @Autowired private LegoSetRepository repository; + protected JdbcTemplate jdbc; + + @BeforeEach + void before() { + + this.jdbc = createJdbcTemplate(createDataSource()); + + try { + this.jdbc.execute(getDropTableStatement()); + } catch (DataAccessException e) {} + + this.jdbc.execute(getCreateTableStatement()); + } + + /** + * Creates a {@link DataSource} to be used in this test. + * + * @return the {@link DataSource} to be used in this test. + */ + protected abstract DataSource createDataSource(); + + /** + * Creates a {@link ConnectionFactory} to be used in this test. + * + * @return the {@link ConnectionFactory} to be used in this test. + */ + protected abstract ConnectionFactory createConnectionFactory(); + + /** + * Returns the CREATE TABLE statement for table {@code legoset} with the following three columns: + *
    + *
  • id integer (primary key), not null, auto-increment
  • + *
  • name varchar(255), nullable
  • + *
  • manual integer, nullable
  • + *
+ * + * @return the CREATE TABLE statement for table {@code legoset} with three columns. + */ + protected abstract String getCreateTableStatement(); + + /** + * Returns the the DROP TABLE statement for table {@code LegoSet}. + * + * @return the DROP TABLE statement for table {@code LegoSet}. + */ + protected abstract String getDropTableStatement(); + + @Test + void insertAndReadEntities() { + + LegoSet legoSet1 = new LegoSet(null, "SCHAUFELRADBAGGER", 12); + LegoSet legoSet2 = new LegoSet(null, "FORSCHUNGSSCHIFF", 13); + + repository.saveAll(Arrays.asList(legoSet1, legoSet2)) // + .as(StepVerifier::create) // + .expectNextCount(2) // + .verifyComplete(); + + List legoSets = repository // + .findAll() // + .collectList() // + .block(Duration.ofMillis(500)); + + assertThat(legoSets).containsExactlyInAnyOrder(legoSet1, legoSet2); + } + + interface LegoSetRepository extends ReactiveCrudRepository {} + + @Getter + @Setter + @Table("LegoSet") + @NoArgsConstructor + public static class LegoSet { + + @Nullable @Column("Id") @Id Integer id; + + @Column("Name") String name; + + @Column("Manual") Integer manual; + + @PersistenceConstructor + LegoSet(@Nullable Integer id, String name, Integer manual) { + this.id = id; + this.name = name; + this.manual = manual; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + LegoSet legoSet = (LegoSet) o; + return Objects.equals(id, legoSet.id) && Objects.equals(name, legoSet.name) + && Objects.equals(manual, legoSet.manual); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, manual); + } + } +} diff --git a/src/test/java/org/springframework/data/r2dbc/repository/H2R2dbcRepositoryWithMixedCaseNamesIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/H2R2dbcRepositoryWithMixedCaseNamesIntegrationTests.java new file mode 100644 index 00000000..743d744f --- /dev/null +++ b/src/test/java/org/springframework/data/r2dbc/repository/H2R2dbcRepositoryWithMixedCaseNamesIntegrationTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021 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.r2dbc.repository; + +import io.r2dbc.spi.ConnectionFactory; + +import java.util.Optional; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration; +import org.springframework.data.r2dbc.convert.R2dbcCustomConversions; +import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; +import org.springframework.data.r2dbc.testing.H2TestSupport; +import org.springframework.data.relational.core.mapping.NamingStrategy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Integration tests for {@link LegoSetRepository} with table and column names that contain * upper and lower case + * characters against H2. + * + * @author Jens Schauder + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class H2R2dbcRepositoryWithMixedCaseNamesIntegrationTests + extends AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests { + + @Configuration + @EnableR2dbcRepositories(considerNestedRepositories = true, + includeFilters = @Filter(classes = { LegoSetRepository.class }, type = FilterType.ASSIGNABLE_TYPE)) + static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration { + + @Bean + @Override + public ConnectionFactory connectionFactory() { + return H2TestSupport.createConnectionFactory(); + } + + @Override + public R2dbcMappingContext r2dbcMappingContext(Optional namingStrategy, + R2dbcCustomConversions r2dbcCustomConversions) { + + R2dbcMappingContext r2dbcMappingContext = super.r2dbcMappingContext(namingStrategy, r2dbcCustomConversions); + r2dbcMappingContext.setForceQuote(true); + + return r2dbcMappingContext; + } + } + + @Override + protected DataSource createDataSource() { + return H2TestSupport.createDataSource(); + } + + @Override + protected ConnectionFactory createConnectionFactory() { + return H2TestSupport.createConnectionFactory(); + } + + @Override + protected String getCreateTableStatement() { + return H2TestSupport.CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } + + @Override + protected String getDropTableStatement() { + return H2TestSupport.DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } +} diff --git a/src/test/java/org/springframework/data/r2dbc/repository/MariaDbR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/MariaDbR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java new file mode 100644 index 00000000..5227204b --- /dev/null +++ b/src/test/java/org/springframework/data/r2dbc/repository/MariaDbR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021 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.r2dbc.repository; + +import io.r2dbc.spi.ConnectionFactory; + +import java.util.Optional; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration; +import org.springframework.data.r2dbc.convert.R2dbcCustomConversions; +import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; +import org.springframework.data.r2dbc.testing.ExternalDatabase; +import org.springframework.data.r2dbc.testing.MariaDbTestSupport; +import org.springframework.data.relational.core.mapping.NamingStrategy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Integration tests for {@link LegoSetRepository} with table and column names that contain * upper and lower case + * characters against MariaDb. + * + * @author Mark Paluch + * @author Zsombor Gegesy + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class MariaDbR2dbcRepositoryWithMixedCaseNamesIntegrationTests + extends AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests { + + @RegisterExtension public static final ExternalDatabase database = MariaDbTestSupport.database(); + + @Configuration + @EnableR2dbcRepositories(considerNestedRepositories = true, + includeFilters = @Filter(classes = { LegoSetRepository.class }, type = FilterType.ASSIGNABLE_TYPE)) + static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration { + + @Bean + @Override + public ConnectionFactory connectionFactory() { + return MariaDbTestSupport.createConnectionFactory(database); + } + + @Override + public R2dbcMappingContext r2dbcMappingContext(Optional namingStrategy, + R2dbcCustomConversions r2dbcCustomConversions) { + + R2dbcMappingContext r2dbcMappingContext = super.r2dbcMappingContext(namingStrategy, r2dbcCustomConversions); + r2dbcMappingContext.setForceQuote(true); + + return r2dbcMappingContext; + } + } + + @Override + protected DataSource createDataSource() { + return MariaDbTestSupport.createDataSource(database); + } + + @Override + protected ConnectionFactory createConnectionFactory() { + return MariaDbTestSupport.createConnectionFactory(database); + } + + @Override + protected String getCreateTableStatement() { + return MariaDbTestSupport.CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } + + @Override + protected String getDropTableStatement() { + return MariaDbTestSupport.DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } +} diff --git a/src/test/java/org/springframework/data/r2dbc/repository/MySqlR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/MySqlR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java new file mode 100644 index 00000000..e8a5d24b --- /dev/null +++ b/src/test/java/org/springframework/data/r2dbc/repository/MySqlR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2021 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.r2dbc.repository; + +import io.r2dbc.spi.ConnectionFactory; + +import java.util.Optional; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration; +import org.springframework.data.r2dbc.convert.R2dbcCustomConversions; +import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; +import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory; +import org.springframework.data.r2dbc.testing.ExternalDatabase; +import org.springframework.data.r2dbc.testing.MySqlTestSupport; +import org.springframework.data.relational.core.mapping.NamingStrategy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Integration tests for {@link LegoSetRepository} with table and column names that contain + * * upper and lower case characters against MySql. + * + * @author Mark Paluch + * @author Zsombor Gegesy + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class MySqlR2dbcRepositoryWithMixedCaseNamesIntegrationTests + extends AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests { + + @RegisterExtension public static final ExternalDatabase database = MySqlTestSupport.database(); + + @Configuration + @EnableR2dbcRepositories(considerNestedRepositories = true, + includeFilters = @Filter(classes = { LegoSetRepository.class }, type = FilterType.ASSIGNABLE_TYPE)) + static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration { + + @Bean + @Override + public ConnectionFactory connectionFactory() { + return MySqlTestSupport.createConnectionFactory(database); + } + + @Override + public R2dbcMappingContext r2dbcMappingContext(Optional namingStrategy, + R2dbcCustomConversions r2dbcCustomConversions) { + + R2dbcMappingContext r2dbcMappingContext = super.r2dbcMappingContext(namingStrategy, r2dbcCustomConversions); + r2dbcMappingContext.setForceQuote(true); + + return r2dbcMappingContext; + } + } + + @Override + protected DataSource createDataSource() { + return MySqlTestSupport.createDataSource(database); + } + + @Override + protected ConnectionFactory createConnectionFactory() { + return MySqlTestSupport.createConnectionFactory(database); + } + + @Override + protected String getCreateTableStatement() { + return MySqlTestSupport.CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } + + @Override + protected String getDropTableStatement() { + return MySqlTestSupport.DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } +} diff --git a/src/test/java/org/springframework/data/r2dbc/repository/OracleR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/OracleR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java new file mode 100644 index 00000000..68c09c2a --- /dev/null +++ b/src/test/java/org/springframework/data/r2dbc/repository/OracleR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021 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.r2dbc.repository; + +import io.r2dbc.spi.ConnectionFactory; + +import java.util.Optional; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration; +import org.springframework.data.r2dbc.convert.R2dbcCustomConversions; +import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; +import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory; +import org.springframework.data.r2dbc.testing.ExternalDatabase; +import org.springframework.data.r2dbc.testing.OracleTestSupport; +import org.springframework.data.relational.core.mapping.NamingStrategy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Integration tests for {@link LegoSetRepository} with table and column names that contain + * * upper and lower case characters against Oracle. + * + * @author Jens Schauder + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class OracleR2dbcRepositoryWithMixedCaseNamesIntegrationTests + extends AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests { + + @RegisterExtension public static final ExternalDatabase database = OracleTestSupport.database(); + + @Configuration + @EnableR2dbcRepositories(considerNestedRepositories = true, + includeFilters = @Filter(classes = { LegoSetRepository.class }, type = FilterType.ASSIGNABLE_TYPE)) + static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration { + + @Bean + @Override + public ConnectionFactory connectionFactory() { + return OracleTestSupport.createConnectionFactory(database); + } + + @Override + public R2dbcMappingContext r2dbcMappingContext(Optional namingStrategy, + R2dbcCustomConversions r2dbcCustomConversions) { + + R2dbcMappingContext r2dbcMappingContext = super.r2dbcMappingContext(namingStrategy, r2dbcCustomConversions); + r2dbcMappingContext.setForceQuote(true); + + return r2dbcMappingContext; + } + } + + @Override + protected DataSource createDataSource() { + return OracleTestSupport.createDataSource(database); + } + + @Override + protected ConnectionFactory createConnectionFactory() { + return OracleTestSupport.createConnectionFactory(database); + } + + @Override + protected String getCreateTableStatement() { + return OracleTestSupport.CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } + + @Override + protected String getDropTableStatement() { + return OracleTestSupport.DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } +} diff --git a/src/test/java/org/springframework/data/r2dbc/repository/PostgresR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/PostgresR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java new file mode 100644 index 00000000..4dce8f79 --- /dev/null +++ b/src/test/java/org/springframework/data/r2dbc/repository/PostgresR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021 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.r2dbc.repository; + +import io.r2dbc.spi.ConnectionFactory; + +import java.util.Optional; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration; +import org.springframework.data.r2dbc.convert.R2dbcCustomConversions; +import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; +import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory; +import org.springframework.data.r2dbc.testing.ExternalDatabase; +import org.springframework.data.r2dbc.testing.PostgresTestSupport; +import org.springframework.data.relational.core.mapping.NamingStrategy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Integration tests for {@link LegoSetRepository} with table and column names that contain + * * upper and lower case characters against Postgres. + * + * @author Jens Schauder + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class PostgresR2dbcRepositoryWithMixedCaseNamesIntegrationTests + extends AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests { + + @RegisterExtension public static final ExternalDatabase database = PostgresTestSupport.database(); + + @Configuration + @EnableR2dbcRepositories(considerNestedRepositories = true, + includeFilters = @Filter(classes = { LegoSetRepository.class }, type = FilterType.ASSIGNABLE_TYPE)) + static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration { + + @Bean + @Override + public ConnectionFactory connectionFactory() { + return PostgresTestSupport.createConnectionFactory(database); + } + + @Override + public R2dbcMappingContext r2dbcMappingContext(Optional namingStrategy, + R2dbcCustomConversions r2dbcCustomConversions) { + + R2dbcMappingContext r2dbcMappingContext = super.r2dbcMappingContext(namingStrategy, r2dbcCustomConversions); + r2dbcMappingContext.setForceQuote(true); + + return r2dbcMappingContext; + } + } + + @Override + protected DataSource createDataSource() { + return PostgresTestSupport.createDataSource(database); + } + + @Override + protected ConnectionFactory createConnectionFactory() { + return PostgresTestSupport.createConnectionFactory(database); + } + + @Override + protected String getCreateTableStatement() { + return PostgresTestSupport.CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } + + @Override + protected String getDropTableStatement() { + return PostgresTestSupport.DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } +} diff --git a/src/test/java/org/springframework/data/r2dbc/repository/SqlServerR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/SqlServerR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java new file mode 100644 index 00000000..fa05057e --- /dev/null +++ b/src/test/java/org/springframework/data/r2dbc/repository/SqlServerR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2019-2021 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.r2dbc.repository; + +import io.r2dbc.spi.ConnectionFactory; + +import java.util.Optional; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration; +import org.springframework.data.r2dbc.convert.R2dbcCustomConversions; +import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; +import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory; +import org.springframework.data.r2dbc.testing.ExternalDatabase; +import org.springframework.data.r2dbc.testing.SqlServerTestSupport; +import org.springframework.data.relational.core.mapping.NamingStrategy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Integration tests for {@link LegoSetRepository} with table and column names that contain + * * upper and lower case characters against SQL-Server. + * + * @author Jens Schauder + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class SqlServerR2dbcRepositoryWithMixedCaseNamesIntegrationTests + extends AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests { + + @RegisterExtension public static final ExternalDatabase database = SqlServerTestSupport.database(); + + @Configuration + @EnableR2dbcRepositories(considerNestedRepositories = true, + includeFilters = @Filter(classes = { LegoSetRepository.class }, type = FilterType.ASSIGNABLE_TYPE)) + static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration { + + @Bean + @Override + public ConnectionFactory connectionFactory() { + return SqlServerTestSupport.createConnectionFactory(database); + } + + @Override + public R2dbcMappingContext r2dbcMappingContext(Optional namingStrategy, + R2dbcCustomConversions r2dbcCustomConversions) { + + R2dbcMappingContext r2dbcMappingContext = super.r2dbcMappingContext(namingStrategy, r2dbcCustomConversions); + r2dbcMappingContext.setForceQuote(true); + + return r2dbcMappingContext; + } + } + + @Override + protected DataSource createDataSource() { + return SqlServerTestSupport.createDataSource(database); + } + + @Override + protected ConnectionFactory createConnectionFactory() { + return SqlServerTestSupport.createConnectionFactory(database); + } + + @Override + protected String getCreateTableStatement() { + return SqlServerTestSupport.CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } + + @Override + protected String getDropTableStatement() { + return SqlServerTestSupport.DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES; + } +} diff --git a/src/test/java/org/springframework/data/r2dbc/testing/ExternalDatabase.java b/src/test/java/org/springframework/data/r2dbc/testing/ExternalDatabase.java index 6e3c7049..74d363b4 100644 --- a/src/test/java/org/springframework/data/r2dbc/testing/ExternalDatabase.java +++ b/src/test/java/org/springframework/data/r2dbc/testing/ExternalDatabase.java @@ -222,7 +222,7 @@ boolean checkValidity() { */ @Override public String getHostname() { - throw new UnsupportedOperationException(getClass().getSimpleName()); + return "unknown"; } /* (non-Javadoc) @@ -230,7 +230,7 @@ public String getHostname() { */ @Override public int getPort() { - throw new UnsupportedOperationException(getClass().getSimpleName()); + return -99999; } /* (non-Javadoc) diff --git a/src/test/java/org/springframework/data/r2dbc/testing/H2TestSupport.java b/src/test/java/org/springframework/data/r2dbc/testing/H2TestSupport.java index 4142ea0b..a09ec752 100644 --- a/src/test/java/org/springframework/data/r2dbc/testing/H2TestSupport.java +++ b/src/test/java/org/springframework/data/r2dbc/testing/H2TestSupport.java @@ -28,11 +28,12 @@ * * @author Mark Paluch * @author Bogdan Ilchyshyn + * @author Jens Schauder */ public class H2TestSupport { public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" // - + " id integer CONSTRAINT id PRIMARY KEY,\n" // + + " id integer CONSTRAINT id1 PRIMARY KEY,\n" // + " version integer NULL,\n" // + " name varchar(255) NOT NULL,\n" // + " manual integer NULL\n," // @@ -40,13 +41,21 @@ public class H2TestSupport { + ");"; public static String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" // - + " id serial CONSTRAINT id PRIMARY KEY,\n" // + + " id serial CONSTRAINT id1 PRIMARY KEY,\n" // + " version integer NULL,\n" // + " name varchar(255) NOT NULL,\n" // + " extra varchar(255),\n" // + " manual integer NULL\n" // + ");"; + public static String CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "CREATE TABLE \"LegoSet\" (\n" // + + " \"Id\" serial CONSTRAINT id2 PRIMARY KEY,\n" // + + " \"Name\" varchar(255) NOT NULL,\n" // + + " \"Manual\" integer NULL\n" // + + ");"; + + public static final String DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "DROP TABLE \"LegoSet\""; + /** * Creates a new {@link ConnectionFactory}. */ diff --git a/src/test/java/org/springframework/data/r2dbc/testing/MariaDbTestSupport.java b/src/test/java/org/springframework/data/r2dbc/testing/MariaDbTestSupport.java index 041c065d..8c93204b 100644 --- a/src/test/java/org/springframework/data/r2dbc/testing/MariaDbTestSupport.java +++ b/src/test/java/org/springframework/data/r2dbc/testing/MariaDbTestSupport.java @@ -35,24 +35,33 @@ * Utility class for testing against MariaDB. * * @author Mark Paluch + * @author Jens Schauder */ public class MariaDbTestSupport { private static ExternalDatabase testContainerDatabase; - public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" // + public static final String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" // + " id integer PRIMARY KEY,\n" // + " name varchar(255) NOT NULL,\n" // + " manual integer NULL\n," // + " cert varbinary(255) NULL\n" // + ") ENGINE=InnoDB;"; - public static String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" // + public static final String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" // + " id integer AUTO_INCREMENT PRIMARY KEY,\n" // + " name varchar(255) NOT NULL,\n" // + " manual integer NULL\n" // + ") ENGINE=InnoDB;"; + public static final String CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "CREATE TABLE `LegoSet` (\n" // + + " `Id` integer AUTO_INCREMENT PRIMARY KEY,\n" // + + " `Name` varchar(255) NOT NULL,\n" // + + " `Manual` integer NULL\n" // + + ") ENGINE=InnoDB;"; + + public static final String DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "DROP TABLE `LegoSet`"; + /** * Returns a database either hosted locally at {@code localhost:3306/mysql} or running inside Docker. * diff --git a/src/test/java/org/springframework/data/r2dbc/testing/MySqlTestSupport.java b/src/test/java/org/springframework/data/r2dbc/testing/MySqlTestSupport.java index bf859dd1..7a1937c4 100644 --- a/src/test/java/org/springframework/data/r2dbc/testing/MySqlTestSupport.java +++ b/src/test/java/org/springframework/data/r2dbc/testing/MySqlTestSupport.java @@ -35,6 +35,7 @@ * * @author Mark Paluch * @author Bogdan Ilchyshyn + * @author Jens Schauder */ public class MySqlTestSupport { @@ -55,6 +56,14 @@ public class MySqlTestSupport { + " manual integer NULL\n" // + ") ENGINE=InnoDB;"; + + public static final String CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "CREATE TABLE `LegoSet` (\n" // + + " `Id` integer AUTO_INCREMENT PRIMARY KEY,\n" // + + " `Name` varchar(255) NOT NULL,\n" // + + " `Manual` integer NULL\n" // + + ") ENGINE=InnoDB;"; + + public static final String DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "DROP TABLE LegoSet"; /** * Returns a database either hosted locally or running inside Docker. * diff --git a/src/test/java/org/springframework/data/r2dbc/testing/OracleTestSupport.java b/src/test/java/org/springframework/data/r2dbc/testing/OracleTestSupport.java index 66b0b0e3..f7ef83dc 100644 --- a/src/test/java/org/springframework/data/r2dbc/testing/OracleTestSupport.java +++ b/src/test/java/org/springframework/data/r2dbc/testing/OracleTestSupport.java @@ -37,6 +37,7 @@ * Utility class for testing against Oracle. * * @author Mark Paluch + * @author Jens Schauder */ public class OracleTestSupport { @@ -57,6 +58,12 @@ public class OracleTestSupport { + " manual INTEGER NULL\n" // + ")"; + public static final String CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "CREATE TABLE \"LegoSet\" (\n" // + + " \"Id\" INTEGER GENERATED by default on null as IDENTITY PRIMARY KEY,\n" // + + " \"Name\" VARCHAR2(255) NOT NULL,\n" // + + " \"Manual\" INTEGER NULL\n" // + + ")"; + public static final String DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "DROP TABLE \"LegoSet\""; /** * Returns a database either hosted locally or running inside Docker. * diff --git a/src/test/java/org/springframework/data/r2dbc/testing/PostgresTestSupport.java b/src/test/java/org/springframework/data/r2dbc/testing/PostgresTestSupport.java index 2be63c9b..b8c26e93 100644 --- a/src/test/java/org/springframework/data/r2dbc/testing/PostgresTestSupport.java +++ b/src/test/java/org/springframework/data/r2dbc/testing/PostgresTestSupport.java @@ -25,7 +25,7 @@ public class PostgresTestSupport { private static ExternalDatabase testContainerDatabase; public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" // - + " id integer CONSTRAINT id PRIMARY KEY,\n" // + + " id integer CONSTRAINT id1 PRIMARY KEY,\n" // + " version integer NULL,\n" // + " name varchar(255) NOT NULL,\n" // + " manual integer NULL\n," // @@ -33,13 +33,20 @@ public class PostgresTestSupport { + ");"; public static String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" // - + " id serial CONSTRAINT id PRIMARY KEY,\n" // + + " id serial CONSTRAINT id1 PRIMARY KEY,\n" // + " version integer NULL,\n" // + " name varchar(255) NOT NULL,\n" // + " extra varchar(255),\n" // + " manual integer NULL\n" // + ");"; + public static final String CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "CREATE TABLE \"LegoSet\" (\n" // + + " \"Id\" serial CONSTRAINT id2 PRIMARY KEY,\n" // + + " \"Name\" varchar(255) NOT NULL,\n" // + + " \"Manual\" integer NULL\n" // + + ");"; + + public static final String DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "DROP TABLE \"LegoSet\""; /** * Returns a database either hosted locally at {@code postgres:@localhost:5432/postgres} or running inside Docker. * diff --git a/src/test/java/org/springframework/data/r2dbc/testing/SqlServerTestSupport.java b/src/test/java/org/springframework/data/r2dbc/testing/SqlServerTestSupport.java index b3c9bad8..5fd65ed3 100644 --- a/src/test/java/org/springframework/data/r2dbc/testing/SqlServerTestSupport.java +++ b/src/test/java/org/springframework/data/r2dbc/testing/SqlServerTestSupport.java @@ -13,9 +13,9 @@ * * @author Mark Paluch * @author Bogdan Ilchyshyn + * @author Jens Schauder */ public class SqlServerTestSupport { - public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" // + " id integer PRIMARY KEY,\n" // + " version integer NULL,\n" // @@ -34,6 +34,14 @@ public class SqlServerTestSupport { public static String INSERT_INTO_LEGOSET = "INSERT INTO legoset (id, name, manual) VALUES(@P0, @P1, @P3)"; + public static final String CREATE_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "CREATE TABLE LegoSet (\n" // + + " Id integer IDENTITY(1,1) PRIMARY KEY,\n" // + + " Name varchar(255) NOT NULL,\n" // + + " Manual integer NULL\n" // + + ");"; + + public static final String DROP_TABLE_LEGOSET_WITH_MIXED_CASE_NAMES = "DROP TABLE LegoSet"; + /** * Returns a locally provided database at {@code sqlserver:@localhost:1433/master}. */