diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/ColumnNameExistenceAwareResultSetReader.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/ColumnNameExistenceAwareResultSetReader.java index f5e1c87d55a..6ee76d45e0a 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/ColumnNameExistenceAwareResultSetReader.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/ColumnNameExistenceAwareResultSetReader.java @@ -55,12 +55,16 @@ private boolean containsColumnName(ResultSet resultSet, String name) { int columnsCount = rsmd.getColumnCount(); knownColumns = CollectionUtils.newHashSet(columnsCount); for (int x = 1; x <= columnsCount; x++) { - knownColumns.add(rsmd.getColumnLabel(x).toLowerCase()); + knownColumns.add(toLowerCase(rsmd.getColumnLabel(x))); } } catch (SQLException e) { throw new RuntimeException(e); } } - return knownColumns.contains(name); + return knownColumns.contains(toLowerCase(name)); + } + + private static String toLowerCase(String str) { + return str == null ? null : str.toLowerCase(); } } diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2RepositorySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2RepositorySpec.groovy index 321920cf961..ffd91365258 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2RepositorySpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2RepositorySpec.groovy @@ -99,6 +99,9 @@ class H2RepositorySpec extends AbstractRepositorySpec implements H2TestPropertyP @Shared H2BookEntityRepository bookEntityRepository = context.getBean(H2BookEntityRepository) + @Shared + H2ExampleEntityRepository exampleEntityRepo = context.getBean(H2ExampleEntityRepository) + @Override EntityWithIdClassRepository getEntityWithIdClassRepository() { return entityWithIdClassRepo @@ -221,6 +224,11 @@ class H2RepositorySpec extends AbstractRepositorySpec implements H2TestPropertyP return pageRepo } + @Override + ExampleEntityRepository getExampleEntityRepository() { + return exampleEntityRepo + } + @Override boolean isSupportsArrays() { return true diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mariadb/MariaRepositorySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mariadb/MariaRepositorySpec.groovy index aff08b06547..1266afa978b 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mariadb/MariaRepositorySpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mariadb/MariaRepositorySpec.groovy @@ -169,6 +169,12 @@ class MariaRepositorySpec extends AbstractRepositorySpec implements MariaTestPro return context.getBean(MySqlEntityWithIdClass2Repository) } + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(MySqlExampleEntityRepository) + } + @Override protected boolean skipCustomSchemaAndCatalogTest() { // INSERT command denied to user 'test'@'172.17.0.1' for table 'cars' diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mysql/MySqlRepositorySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mysql/MySqlRepositorySpec.groovy index 5110f9a0202..c77a56975fd 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mysql/MySqlRepositorySpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mysql/MySqlRepositorySpec.groovy @@ -167,6 +167,12 @@ class MySqlRepositorySpec extends AbstractRepositorySpec implements MySQLTestPro return context.getBean(MySqlEntityWithIdClass2Repository) } + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(MySqlExampleEntityRepository) + } + def "for update is in the correct location"() { given: setupBooks() diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXERepositorySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXERepositorySpec.groovy index 905b2bf4798..d02889b65fd 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXERepositorySpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXERepositorySpec.groovy @@ -173,6 +173,12 @@ class OracleXERepositorySpec extends AbstractRepositorySpec implements OracleTes return context.getBean(OracleXEEntityWithIdClass2Repository) } + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(OracleExampleEntityRepository) + } + @Override protected boolean skipCustomSchemaAndCatalogTest() { // ORA-04043: object "FORD"."CARS" does not exist diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresRepositorySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresRepositorySpec.groovy index 1dbf3640ccc..f56f898b346 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresRepositorySpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresRepositorySpec.groovy @@ -174,6 +174,12 @@ class PostgresRepositorySpec extends AbstractRepositorySpec implements PostgresT return context.getBean(PostgresEntityWithIdClass2Repository) } + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(PostgresExampleEntityRepository) + } + @Memoized @Override boolean isSupportsArrays() { diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/sqlserver/SqlServerRepositorySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/sqlserver/SqlServerRepositorySpec.groovy index d7daacc5233..d9375262756 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/sqlserver/SqlServerRepositorySpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/sqlserver/SqlServerRepositorySpec.groovy @@ -164,4 +164,10 @@ class SqlServerRepositorySpec extends AbstractRepositorySpec implements MSSQLTes EntityWithIdClass2Repository getEntityWithIdClass2Repository() { return context.getBean(MSEntityWithIdClass2Repository) } + + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(MSExampleEntityRepository) + } } diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/h2/H2ExampleEntityRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/h2/H2ExampleEntityRepository.java new file mode 100644 index 00000000000..7a56efb4226 --- /dev/null +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/h2/H2ExampleEntityRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.jdbc.h2; + +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.tck.repositories.ExampleEntityRepository; + +@JdbcRepository(dialect = Dialect.H2) +public interface H2ExampleEntityRepository extends ExampleEntityRepository { +} diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/mysql/MySqlExampleEntityRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/mysql/MySqlExampleEntityRepository.java new file mode 100644 index 00000000000..57b16ad0412 --- /dev/null +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/mysql/MySqlExampleEntityRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.jdbc.mysql; + +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.tck.repositories.ExampleEntityRepository; + +@JdbcRepository(dialect = Dialect.MYSQL) +public interface MySqlExampleEntityRepository extends ExampleEntityRepository { +} diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleExampleEntityRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleExampleEntityRepository.java new file mode 100644 index 00000000000..74c90ebaa8a --- /dev/null +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleExampleEntityRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.jdbc.oraclexe; + +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.tck.repositories.ExampleEntityRepository; + +@JdbcRepository(dialect = Dialect.ORACLE) +public interface OracleExampleEntityRepository extends ExampleEntityRepository { +} diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/postgres/PostgresExampleEntityRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/postgres/PostgresExampleEntityRepository.java new file mode 100644 index 00000000000..ff29dd7b402 --- /dev/null +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/postgres/PostgresExampleEntityRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.jdbc.postgres; + +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.tck.repositories.ExampleEntityRepository; + +@JdbcRepository(dialect = Dialect.POSTGRES) +public interface PostgresExampleEntityRepository extends ExampleEntityRepository { +} diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/sqlserver/MSExampleEntityRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/sqlserver/MSExampleEntityRepository.java new file mode 100644 index 00000000000..e776bf5ea6d --- /dev/null +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/sqlserver/MSExampleEntityRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.jdbc.sqlserver; + +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.tck.repositories.ExampleEntityRepository; + +@JdbcRepository(dialect = Dialect.SQL_SERVER) +public interface MSExampleEntityRepository extends ExampleEntityRepository { +} diff --git a/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java b/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java index 29c5cb336b1..bd0fb0670b7 100644 --- a/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java +++ b/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java @@ -163,24 +163,21 @@ protected R convertResult(CodecRegistry codecRegistry, if (resultType == BsonDocument.class) { return (R) result; } - Optional> introspection = BeanIntrospector.SHARED.findIntrospection(resultType); - if (introspection.isPresent()) { - try { - return mapIntrospectedObject(result, resultType); - } catch (Exception e) { - LOG.warn("Failed to map @Introspection annotated result. " + - "Now attempting to fallback and read object from the document. Error: {}", e.getMessage()); - } + + Optional maybeConverted = convertUsingIntrospected(result, resultType); + if (maybeConverted.isPresent()) { + return maybeConverted.get(); } + BsonValue value; if (result == null) { value = BsonNull.VALUE; } else if (result.size() == 1) { value = result.values().iterator().next(); } else if (result.size() == 2) { - Optional> id = result.entrySet().stream().filter(f -> !f.getKey().equals("_id")).findFirst(); - if (id.isPresent()) { - value = id.get().getValue(); + Optional> nonIdValue = result.entrySet().stream().filter(f -> !f.getKey().equals("_id")).findFirst(); + if (nonIdValue.isPresent()) { + value = nonIdValue.get().getValue(); } else { value = result.values().iterator().next(); } @@ -196,6 +193,42 @@ protected R convertResult(CodecRegistry codecRegistry, return conversionService.convertRequired(MongoUtils.toValue(value), resultType); } + /** + * Attempts to convert a BSON document into an instance of the specified result type using introspection. + * + * If the result type has been annotated with `@Introspection`, this method will attempt to use the provided + * `BeanIntrospection` to map the BSON document onto an instance of the result type. + * + * If the mapping fails or if no `BeanIntrospection` is found for the result type, an empty `Optional` is returned. + * + * @param result The BSON document containing the data to be mapped + * @param resultType The type of the object being mapped + * @param The type parameter representing the result type + * @return An `Optional` containing the mapped object, or an empty `Optional` if mapping failed or no `BeanIntrospection` was found + */ + protected Optional convertUsingIntrospected(BsonDocument result, Class resultType) { + Optional> introspection = BeanIntrospector.SHARED.findIntrospection(resultType); + if (introspection.isPresent()) { + try { + return Optional.of(mapIntrospectedObject(result, resultType)); + } catch (Exception e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Failed to map @Introspection annotated result. " + + "Now attempting to fallback and read object from the document. Error: {}", e.getMessage()); + } + } + } + return Optional.empty(); + } + + /** + * Maps an introspected object from a BSON document. + * + * @param result The BSON document containing the data to be mapped + * @param resultType The type of the object being mapped + * @param The type parameter representing the result type + * @return An instance of the specified result type, populated with data from the BSON document + */ private R mapIntrospectedObject(BsonDocument result, Class resultType) { return (new BeanIntrospectionMapper() { @Override diff --git a/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy b/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy index a0639c4c11d..888cde4f077 100644 --- a/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy +++ b/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy @@ -10,8 +10,10 @@ import com.mongodb.client.model.Updates import groovy.transform.Memoized import io.micronaut.data.document.mongodb.entities.ComplexEntity import io.micronaut.data.document.mongodb.entities.ComplexValue +import io.micronaut.data.document.mongodb.entities.Customer import io.micronaut.data.document.mongodb.entities.ElementRow import io.micronaut.data.document.mongodb.repositories.ComplexEntityRepository +import io.micronaut.data.document.mongodb.repositories.CustomerRepository import io.micronaut.data.document.mongodb.repositories.ElementRowRepository import io.micronaut.data.document.mongodb.repositories.MongoAuthorRepository import io.micronaut.data.document.mongodb.repositories.MongoCriteriaPersonRepository @@ -892,6 +894,23 @@ class MongoDocumentRepositorySpec extends AbstractDocumentRepositorySpec impleme people.collect{ it.id }.containsAll(limitedPeople2.collect{ it.id }) } + void "test DTO retrieval"() { + when: + def customer = new Customer("1", "first", "last", List.of()) + def saved = customerRepository.save(customer) + def loaded = customerRepository.findById(saved.id) + then: + loaded.present + loaded.get().id == saved.id + when: + def customerViews = customerRepository.viewFindAll(); + then: + customerViews.size() == 1 + customerViews[0].id == saved.id + cleanup: + customerRepository.deleteAll() + } + @Memoized MongoExecutorPersonRepository getMongoExecutorPersonRepository() { return context.getBean(MongoExecutorPersonRepository) @@ -959,4 +978,9 @@ class MongoDocumentRepositorySpec extends AbstractDocumentRepositorySpec impleme ComplexEntityRepository getComplexEntityRepository() { return context.getBean(ComplexEntityRepository) } + + @Memoized + CustomerRepository getCustomerRepository() { + return context.getBean(CustomerRepository) + } } diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ChangeLog.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ChangeLog.java new file mode 100644 index 00000000000..996bc27e4a8 --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ChangeLog.java @@ -0,0 +1,4 @@ +package io.micronaut.data.document.mongodb.entities; + +public record ChangeLog(String message) { +} diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/Customer.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/Customer.java new file mode 100644 index 00000000000..d919f8c5d96 --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/Customer.java @@ -0,0 +1,60 @@ +package io.micronaut.data.document.mongodb.entities; + +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; + +import java.util.List; + +@MappedEntity +public final class Customer { + @Id + @GeneratedValue + private String id; + private String firstName; + private String lastName; + + private List changeLogs; + + public Customer() { + } + + public Customer(String id, String firstName, String lastName, List changeLogs) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.changeLogs = changeLogs; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public List getChangeLogs() { + return changeLogs; + } + + public void setChangeLogs(List changeLogs) { + this.changeLogs = changeLogs; + } +} diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/CustomerView.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/CustomerView.java new file mode 100644 index 00000000000..916c95e43fd --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/CustomerView.java @@ -0,0 +1,11 @@ +package io.micronaut.data.document.mongodb.entities; + +import io.micronaut.core.annotation.Introspected; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonRepresentation; + +import static org.bson.BsonType.OBJECT_ID; + +@Introspected +public record CustomerView(@BsonId @BsonRepresentation(OBJECT_ID) String id, String firstName, String lastName) { +} diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/repositories/CustomerRepository.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/repositories/CustomerRepository.java new file mode 100644 index 00000000000..ca014e887c6 --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/repositories/CustomerRepository.java @@ -0,0 +1,16 @@ +package io.micronaut.data.document.mongodb.repositories; + +import io.micronaut.data.document.mongodb.entities.Customer; +import io.micronaut.data.document.mongodb.entities.CustomerView; +import io.micronaut.data.mongodb.annotation.MongoFindQuery; +import io.micronaut.data.mongodb.annotation.MongoRepository; +import io.micronaut.data.repository.CrudRepository; + +import java.util.List; + +@MongoRepository +public interface CustomerRepository extends CrudRepository { + + @MongoFindQuery(filter = "{}", project = "{ changeLogs: 0}") + List viewFindAll(); +} diff --git a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/h2/H2RepositorySpec.groovy b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/h2/H2RepositorySpec.groovy index 1eaf4806a4d..2fccdef4993 100644 --- a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/h2/H2RepositorySpec.groovy +++ b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/h2/H2RepositorySpec.groovy @@ -164,6 +164,12 @@ class H2RepositorySpec extends AbstractRepositorySpec implements H2TestPropertyP return context.getBean(H2EntityWithIdClass2Repository) } + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(H2ExampleEntityRepository) + } + @Override protected boolean skipQueryByDataArray() { return true diff --git a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mariadb/MariaDbRepositorySpec.groovy b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mariadb/MariaDbRepositorySpec.groovy index d567d0b6a42..290d1eb7bf1 100644 --- a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mariadb/MariaDbRepositorySpec.groovy +++ b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mariadb/MariaDbRepositorySpec.groovy @@ -166,6 +166,12 @@ class MariaDbRepositorySpec extends AbstractRepositorySpec implements MariaDbTes return context.getBean(MySqlPageRepository) } + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(MySqlExampleEntityRepository) + } + @Override protected boolean skipCustomSchemaAndCatalogTest() { // INSERT command denied to user 'test'@'172.17.0.1' for table 'cars' diff --git a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mysql/MySqlRepositorySpec.groovy b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mysql/MySqlRepositorySpec.groovy index c34d002b471..31239a11716 100644 --- a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mysql/MySqlRepositorySpec.groovy +++ b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mysql/MySqlRepositorySpec.groovy @@ -170,6 +170,12 @@ class MySqlRepositorySpec extends AbstractRepositorySpec implements MySqlTestPro return context.getBean(MySqlEntityWithIdClass2Repository) } + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(MySqlExampleEntityRepository) + } + @Override protected boolean skipCustomSchemaAndCatalogTest() { // INSERT command denied to user 'test'@'172.17.0.1' for table 'cars' diff --git a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/oraclexe/OracleXERepositorySpec.groovy b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/oraclexe/OracleXERepositorySpec.groovy index 763594d80ab..c526bc01dab 100644 --- a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/oraclexe/OracleXERepositorySpec.groovy +++ b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/oraclexe/OracleXERepositorySpec.groovy @@ -170,6 +170,12 @@ class OracleXERepositorySpec extends AbstractRepositorySpec implements OracleXET return context.getBean(OracleXEEntityWithIdClass2Repository) } + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(OracleExampleEntityRepository) + } + @Override protected boolean skipCustomSchemaAndCatalogTest() { // ORA-04043: object "FORD"."CARS" does not exist diff --git a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/postgres/PostgresRepositorySpec.groovy b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/postgres/PostgresRepositorySpec.groovy index 8d918300b0e..d293a3fe197 100644 --- a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/postgres/PostgresRepositorySpec.groovy +++ b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/postgres/PostgresRepositorySpec.groovy @@ -165,6 +165,12 @@ class PostgresRepositorySpec extends AbstractRepositorySpec implements PostgresT return context.getBean(PostgresEntityWithIdClass2Repository) } + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(PostgresExampleEntityRepository) + } + @Override boolean isSupportsArrays() { return true diff --git a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/sqlserver/SqlServerRepositorySpec.groovy b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/sqlserver/SqlServerRepositorySpec.groovy index 993cc6526f1..b2e1d775e9e 100644 --- a/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/sqlserver/SqlServerRepositorySpec.groovy +++ b/data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/sqlserver/SqlServerRepositorySpec.groovy @@ -170,6 +170,12 @@ class SqlServerRepositorySpec extends AbstractRepositorySpec implements SqlServe return context.getBean(MSPageRepository) } + @Memoized + @Override + ExampleEntityRepository getExampleEntityRepository() { + return context.getBean(MSExampleEntityRepository) + } + @Override boolean supportsNullCharacter() { false diff --git a/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/h2/H2ExampleEntityRepository.java b/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/h2/H2ExampleEntityRepository.java new file mode 100644 index 00000000000..74c1ef38fb1 --- /dev/null +++ b/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/h2/H2ExampleEntityRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.r2dbc.h2; + +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.r2dbc.annotation.R2dbcRepository; +import io.micronaut.data.tck.repositories.ExampleEntityRepository; + +@R2dbcRepository(dialect = Dialect.H2) +public interface H2ExampleEntityRepository extends ExampleEntityRepository { +} diff --git a/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/mysql/MySqlExampleEntityRepository.java b/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/mysql/MySqlExampleEntityRepository.java new file mode 100644 index 00000000000..3f6434e56be --- /dev/null +++ b/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/mysql/MySqlExampleEntityRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.r2dbc.mysql; + +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.r2dbc.annotation.R2dbcRepository; +import io.micronaut.data.tck.repositories.ExampleEntityRepository; + +@R2dbcRepository(dialect = Dialect.MYSQL) +public interface MySqlExampleEntityRepository extends ExampleEntityRepository { +} diff --git a/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/oraclexe/OracleExampleEntityRepository.java b/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/oraclexe/OracleExampleEntityRepository.java new file mode 100644 index 00000000000..f644dede2af --- /dev/null +++ b/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/oraclexe/OracleExampleEntityRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.r2dbc.oraclexe; + +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.r2dbc.annotation.R2dbcRepository; +import io.micronaut.data.tck.repositories.ExampleEntityRepository; + +@R2dbcRepository(dialect = Dialect.ORACLE) +public interface OracleExampleEntityRepository extends ExampleEntityRepository { +} diff --git a/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/postgres/PostgresExampleEntityRepository.java b/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/postgres/PostgresExampleEntityRepository.java new file mode 100644 index 00000000000..c9aa4ed7757 --- /dev/null +++ b/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/postgres/PostgresExampleEntityRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.r2dbc.postgres; + +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.r2dbc.annotation.R2dbcRepository; +import io.micronaut.data.tck.repositories.ExampleEntityRepository; + +@R2dbcRepository(dialect = Dialect.POSTGRES) +public interface PostgresExampleEntityRepository extends ExampleEntityRepository { +} diff --git a/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/sqlserver/MSExampleEntityRepository.java b/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/sqlserver/MSExampleEntityRepository.java new file mode 100644 index 00000000000..ad69b67c9b9 --- /dev/null +++ b/data-r2dbc/src/test/java/io/micronaut/data/r2dbc/sqlserver/MSExampleEntityRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.r2dbc.sqlserver; + +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.r2dbc.annotation.R2dbcRepository; +import io.micronaut.data.tck.repositories.ExampleEntityRepository; + +@R2dbcRepository(dialect = Dialect.SQL_SERVER) +public interface MSExampleEntityRepository extends ExampleEntityRepository { +} diff --git a/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractRepositorySpec.groovy b/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractRepositorySpec.groovy index c55a31ea82e..36f73c507ff 100644 --- a/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractRepositorySpec.groovy +++ b/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractRepositorySpec.groovy @@ -45,6 +45,7 @@ import io.micronaut.data.tck.entities.CountryRegionCity import io.micronaut.data.tck.entities.EntityIdClass import io.micronaut.data.tck.entities.EntityWithIdClass import io.micronaut.data.tck.entities.EntityWithIdClass2 +import io.micronaut.data.tck.entities.ExampleEntity import io.micronaut.data.tck.entities.Face import io.micronaut.data.tck.entities.Food import io.micronaut.data.tck.entities.Genre @@ -120,6 +121,7 @@ abstract class AbstractRepositorySpec extends Specification { abstract PageRepository getPageRepository() abstract EntityWithIdClassRepository getEntityWithIdClassRepository() abstract EntityWithIdClass2Repository getEntityWithIdClass2Repository() + abstract ExampleEntityRepository getExampleEntityRepository() abstract Map getProperties() @@ -3416,6 +3418,33 @@ abstract class AbstractRepositorySpec extends Specification { books.size() > 0 } + void "test query specification with uppercase/lowercase column names"() { + given: + exampleEntityRepository.save(new ExampleEntity(1, "foo", "bar")) + when: + QuerySpecification qs = (root, query, criteriaBuilder) -> { + query.multiselect( + root.get("id"), + root.get("lowercaseColumn")) + return criteriaBuilder.equal(root.get("id"), 1) + } + def entity = exampleEntityRepository.find(qs) + then: + entity.lowercaseColumn() == "bar" + when: + qs = (root, query, criteriaBuilder) -> { + query.multiselect( + root.get("id"), + root.get("uppercaseColumn")) + return criteriaBuilder.equal(root.get("id"), 1) + } + entity = exampleEntityRepository.find(qs) + then: + entity.uppercaseColumn() == "foo" + cleanup: + exampleEntityRepository.deleteById(1) + } + private GregorianCalendar getYearMonthDay(Date dateCreated) { def cal = dateCreated.toCalendar() def localDate = LocalDate.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH)) diff --git a/data-tck/src/main/java/io/micronaut/data/tck/entities/ExampleEntity.java b/data-tck/src/main/java/io/micronaut/data/tck/entities/ExampleEntity.java new file mode 100644 index 00000000000..1f6da13e600 --- /dev/null +++ b/data-tck/src/main/java/io/micronaut/data/tck/entities/ExampleEntity.java @@ -0,0 +1,13 @@ +package io.micronaut.data.tck.entities; + +import io.micronaut.core.annotation.Nullable; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public record ExampleEntity( + @Id Integer id, + @Nullable @Column(name = "UPPERCASE_COLUMN") String uppercaseColumn, + @Nullable @Column(name = "lowercase_column") String lowercaseColumn) { +} diff --git a/data-tck/src/main/java/io/micronaut/data/tck/repositories/ExampleEntityRepository.java b/data-tck/src/main/java/io/micronaut/data/tck/repositories/ExampleEntityRepository.java new file mode 100644 index 00000000000..243d50cc414 --- /dev/null +++ b/data-tck/src/main/java/io/micronaut/data/tck/repositories/ExampleEntityRepository.java @@ -0,0 +1,13 @@ +package io.micronaut.data.tck.repositories; + +import io.micronaut.data.repository.GenericRepository; +import io.micronaut.data.repository.jpa.criteria.QuerySpecification; +import io.micronaut.data.tck.entities.ExampleEntity; + +public interface ExampleEntityRepository extends GenericRepository { + void save(ExampleEntity entity); + + ExampleEntity find(QuerySpecification querySpecification); + + void deleteById(Integer id); +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 63e0b0ebcaa..5787cafe502 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] micronaut = "4.7.11" -micronaut-platform = "4.6.3" +micronaut-platform = "4.7.4" micronaut-docs = "2.0.0" micronaut-gradle-plugin = "4.4.4" micronaut-testresources = "2.7.0"