diff --git a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java index 02450fbf2e8..afc26cd29b6 100644 --- a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java +++ b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java @@ -108,14 +108,14 @@ protected void appendPropertyProjection(QueryPropertyPath propertyPath) { } @Override - protected void appendAssociationProjection(Association association, PersistentPropertyPath propertyPath) { - String joinedPath = propertyPath.getPath(); + protected void appendAssociationProjection(PersistentAssociationPath associationPath) { + String joinedPath = associationPath.getPath(); if (!queryState.isJoined(joinedPath)) { query.setLength(query.length() - 1); return; } - String joinAlias = queryState.getJoinAlias(propertyPath.getPath()); - selectAllColumns(AnnotationMetadata.EMPTY_METADATA, association.getAssociatedEntity(), joinAlias); + String joinAlias = queryState.getJoinAlias(associationPath.getPath()); + selectAllColumns(AnnotationMetadata.EMPTY_METADATA, associationPath.getAssociation().getAssociatedEntity(), joinAlias); } @Override diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/MyEmbedded.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/MyEmbedded.java new file mode 100644 index 00000000000..4fe5ae730b6 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/MyEmbedded.java @@ -0,0 +1,33 @@ +package io.micronaut.data.jdbc.h2.one2one.select; + +import io.micronaut.data.annotation.AutoPopulated; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; + +import java.util.UUID; + +@MappedEntity +public class MyEmbedded { + + @Id + @AutoPopulated + private UUID id; + + private String someProp; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getSomeProp() { + return someProp; + } + + public void setSomeProp(String someProp) { + this.someProp = someProp; + } +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/MyOrder.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/MyOrder.java new file mode 100644 index 00000000000..8b1fb97c018 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/MyOrder.java @@ -0,0 +1,37 @@ +package io.micronaut.data.jdbc.h2.one2one.select; + +import io.micronaut.core.annotation.Nullable; +import io.micronaut.data.annotation.AutoPopulated; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.Relation; + +import java.util.UUID; + +@MappedEntity +public class MyOrder { + + @Id + @AutoPopulated + private UUID orderId; + + @Nullable + @Relation(value = Relation.Kind.ONE_TO_ONE) + private MyEmbedded embedded; + + public UUID getOrderId() { + return orderId; + } + + public void setOrderId(UUID orderId) { + this.orderId = orderId; + } + + public MyEmbedded getEmbedded() { + return embedded; + } + + public void setEmbedded(MyEmbedded embedded) { + this.embedded = embedded; + } +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/MyOrderRepository.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/MyOrderRepository.java new file mode 100644 index 00000000000..86988b9d7e3 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/MyOrderRepository.java @@ -0,0 +1,23 @@ +package io.micronaut.data.jdbc.h2.one2one.select; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.data.annotation.Join; +import io.micronaut.data.annotation.repeatable.JoinSpecifications; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.Page; +import io.micronaut.data.model.Pageable; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; +import io.micronaut.data.repository.jpa.criteria.PredicateSpecification; + +import java.util.UUID; + +@JdbcRepository(dialect = Dialect.H2) +public interface MyOrderRepository extends CrudRepository { + @NonNull + @JoinSpecifications({ + @Join(value = "embedded", type = Join.Type.LEFT_FETCH) + }) + Page findAll(PredicateSpecification spec, Pageable pageable); + +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/OneToOneProjectionSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/OneToOneProjectionSpec.groovy new file mode 100644 index 00000000000..951f613c698 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2one/select/OneToOneProjectionSpec.groovy @@ -0,0 +1,28 @@ +package io.micronaut.data.jdbc.h2.one2one.select + +import io.micronaut.data.jdbc.h2.H2DBProperties +import io.micronaut.data.model.Pageable +import io.micronaut.data.model.Sort +import io.micronaut.data.repository.jpa.criteria.PredicateSpecification +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import spock.lang.Specification + +@H2DBProperties +@MicronautTest(transactional = false) +class OneToOneProjectionSpec extends Specification { + + @Inject + MyOrderRepository orderRepository + + void findAll_withPageableSort_andSearch() { + given: + Sort.Order.Direction sortDirection = Sort.Order.Direction.ASC + Pageable pageable = Pageable.UNPAGED.order(new Sort.Order("embedded.someProp", sortDirection, false)) + PredicateSpecification predicate = null + when: + orderRepository.findAll(predicate, pageable) + then: + noExceptionThrown() + } +} diff --git a/data-model/src/main/java/io/micronaut/data/model/PersistentPropertyPath.java b/data-model/src/main/java/io/micronaut/data/model/PersistentPropertyPath.java index cc96036363b..02bb4a93a22 100644 --- a/data-model/src/main/java/io/micronaut/data/model/PersistentPropertyPath.java +++ b/data-model/src/main/java/io/micronaut/data/model/PersistentPropertyPath.java @@ -200,6 +200,21 @@ public String getPath() { return path; } + /** + * @return The associations path + */ + @NonNull + public String getAssociationsPath() { + if (associations.isEmpty()) { + return ""; + } + StringJoiner joiner = new StringJoiner("."); + for (Association association : associations) { + joiner.add(association.getName()); + } + return joiner.toString(); + } + /** * @return The array path */ diff --git a/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java b/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java index a935e73765a..5ae1da730e1 100644 --- a/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java +++ b/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java @@ -2512,6 +2512,8 @@ public void visit(io.micronaut.data.model.jpa.criteria.PersistentPropertyPath } else { query.setLength(query.length() - 1); } + } else if (!propertyPath.getAssociations().isEmpty() && queryState.isJoined(propertyPath.getAssociationsPath())) { + appendPropertyProjection(findProperty(propertyPath.getPath())); } else { appendCompoundPropertyProjection(propertyPath); } @@ -2520,7 +2522,7 @@ public void visit(io.micronaut.data.model.jpa.criteria.PersistentPropertyPath query.append(DISTINCT); } if (property instanceof Association association && !property.isEmbedded()) { - appendAssociationProjection(association, propertyPath); + appendAssociationProjection(new PersistentAssociationPath(propertyPath.getAssociations(), association)); } else { appendPropertyProjection(findProperty(propertyPath.getPath())); } @@ -2681,7 +2683,21 @@ protected void appendCompoundPropertyProjection(PersistentPropertyPath propertyP @Internal protected void appendCompoundAssociationProjection(PersistentAssociationPath propertyPath) { if (!query.isEmpty() && query.charAt(query.length() - 1) == ',') { - // Strip last . + // Strip last , + query.setLength(query.length() - 1); + } + selectAllColumnsFromJoinPaths(queryState.baseQueryDefinition().getJoinPaths(), null); + } + + /** + * Appends the compound (part of entity or DTO) association projection. + * + * @param propertyPath The property path + */ + @Internal + protected void appendCompoundProjection(PersistentPropertyPath propertyPath) { + if (!query.isEmpty() && query.charAt(query.length() - 1) == ',') { + // Strip last , query.setLength(query.length() - 1); } selectAllColumnsFromJoinPaths(queryState.baseQueryDefinition().getJoinPaths(), null); @@ -2737,19 +2753,17 @@ protected void appendPropertyProjection(QueryPropertyPath propertyPath) { /** * Appends selection projection for the property which is association. * - * @param association the persistent property - * @param propertyPath the persistent property path + * @param associationPath the persistent property path */ - protected void appendAssociationProjection(Association association, - PersistentPropertyPath propertyPath) { - String joinedPath = propertyPath.getPath(); + protected void appendAssociationProjection(PersistentAssociationPath associationPath) { + String joinedPath = associationPath.getPath(); if (!queryState.isJoined(joinedPath)) { query.setLength(query.length() - 1); return; } - String joinAlias = queryState.findJoinAlias(propertyPath.getPath()); + String joinAlias = queryState.findJoinAlias(associationPath.getPath()); - selectAllColumns(AnnotationMetadata.EMPTY_METADATA, association.getAssociatedEntity(), joinAlias); + selectAllColumns(AnnotationMetadata.EMPTY_METADATA, associationPath.getAssociation().getAssociatedEntity(), joinAlias); Collection joinPaths = queryState.baseQueryDefinition().getJoinPaths(); List newJoinPaths = new ArrayList<>(joinPaths.size()); diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/AbstractSpecificationInterceptor.java b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/AbstractSpecificationInterceptor.java index c4c5140ad6d..b90babcc425 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/AbstractSpecificationInterceptor.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/AbstractSpecificationInterceptor.java @@ -379,9 +379,10 @@ private CriteriaQuery createSelectIdsCriteriaQuery(MethodInvocationCo // We need to select all ordered properties from ORDER BY for DISTINCT to work properly for (Sort.Order order : sort.getOrderBy()) { Path path = root; - for (String next : StringUtils.splitOmitEmptyStrings(order.getProperty(), '.')) { - if (path instanceof From from) { - path = from.join(next); + for (Iterator iterator = StringUtils.splitOmitEmptyStrings(order.getProperty(), '.').iterator(); iterator.hasNext(); ) { + String next = iterator.next(); + if (iterator.hasNext()) { + path = ((From) path).join(next); } else { path = path.get(next); } @@ -587,9 +588,10 @@ private List getOrders(Sort sort, Root root, CriteriaBuilder cb) { List orders = new ArrayList<>(); for (Sort.Order order : sort.getOrderBy()) { Path path = root; - for (String next : StringUtils.splitOmitEmptyStrings(order.getProperty(), '.')) { - if (path instanceof From from) { - path = from.join(next); + for (Iterator iterator = StringUtils.splitOmitEmptyStrings(order.getProperty(), '.').iterator(); iterator.hasNext(); ) { + String next = iterator.next(); + if (iterator.hasNext()) { + path = ((From) path).join(next); } else { path = path.get(next); }