From 8a671907b412b97e0b99ebf325fd262cec4c6786 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 29 Jan 2025 16:41:05 +0700 Subject: [PATCH] Correct multi property order path --- .../data/jdbc/h2/H2EmbeddedIdSpec.groovy | 30 +++++++++++++ .../EmbeddedAssociationJoinSpec.groovy | 22 +++++++++- .../processor/visitors/OrderBySpec.groovy | 42 +++++++++++++++++++ .../AbstractSpecificationInterceptor.java | 21 ++++++++-- 4 files changed, 110 insertions(+), 5 deletions(-) diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2EmbeddedIdSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2EmbeddedIdSpec.groovy index 4a5151d3f05..935658f6652 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2EmbeddedIdSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2EmbeddedIdSpec.groovy @@ -18,6 +18,8 @@ package io.micronaut.data.jdbc.h2 import io.micronaut.core.annotation.Introspected import io.micronaut.data.annotation.* import io.micronaut.data.jdbc.annotation.JdbcRepository +import io.micronaut.data.model.Page +import io.micronaut.data.model.Pageable import io.micronaut.data.model.Sort import io.micronaut.data.model.query.builder.sql.Dialect import io.micronaut.data.repository.CrudRepository @@ -52,6 +54,9 @@ class H2EmbeddedIdSpec extends Specification { } void "test CRUD"() { + given: + repository.deleteAll() + when: ShipmentId id = new ShipmentId("a", "b") repository.save(new Shipment(id, "test")) @@ -156,6 +161,31 @@ class H2EmbeddedIdSpec extends Specification { then:"The entities where deleted" repository.count() == 0 } + + void "test criteria order of embedded"() { + given: + repository.deleteAll() + when: + ShipmentId id = new ShipmentId("a", "b") + repository.save(new Shipment(id, "test")) + + ShipmentId id2 = new ShipmentId("c", "d") + repository.save(new Shipment(id2, "test2")) + + ShipmentId id3 = new ShipmentId("e", "f") + repository.save(new Shipment(id3, "test3")) + + ShipmentId id4 = new ShipmentId("g", "h") + repository.save(new Shipment(id4, "test4")) + + Sort.Order.Direction sortDirection = Sort.Order.Direction.ASC; + Pageable pageable = Pageable.UNPAGED.order(new Sort.Order("shipmentId.city", sortDirection, false)); + def page = repository.findAll(pageable) + + then: + page.totalSize == 4 + page.content[0].shipmentId.city == "b" + } } @Entity diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/embeddedAssociation/EmbeddedAssociationJoinSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/embeddedAssociation/EmbeddedAssociationJoinSpec.groovy index b2147cf7728..a2d1fe3a642 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/embeddedAssociation/EmbeddedAssociationJoinSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/embeddedAssociation/EmbeddedAssociationJoinSpec.groovy @@ -2,11 +2,18 @@ package io.micronaut.data.jdbc.h2.embeddedAssociation import io.micronaut.context.ApplicationContext import io.micronaut.data.annotation.* +import io.micronaut.data.annotation.repeatable.JoinSpecifications import io.micronaut.data.jdbc.annotation.JdbcRepository import io.micronaut.data.jdbc.h2.H2DBProperties import io.micronaut.data.jdbc.h2.H2TestPropertyProvider +import io.micronaut.data.model.Page +import io.micronaut.data.model.Pageable +import io.micronaut.data.model.Sort import io.micronaut.data.model.query.builder.sql.Dialect import io.micronaut.data.repository.CrudRepository +import io.micronaut.data.repository.jpa.JpaSpecificationExecutor +import io.micronaut.data.repository.jpa.criteria.PredicateSpecification +import io.micronaut.data.tck.entities.Order import io.micronaut.test.extensions.spock.annotation.MicronautTest import spock.lang.AutoCleanup import spock.lang.Shared @@ -64,6 +71,11 @@ class EmbeddedAssociationJoinSpec extends Specification implements H2TestPropert when: mainEntityRepository.save(e) e = mainEntityRepository.findById(e.id).get() + Sort.Order.Direction sortDirection = Sort.Order.Direction.ASC; + Pageable pageable = Pageable.UNPAGED.order(new Sort.Order("child.name", sortDirection, false)); + mainEntityRepository.findAll(pageable).totalPages == 1 + PredicateSpecification predicate = null + mainEntityRepository.findAllByCriteria(predicate, pageable).totalPages == 1 then: e.id e.assoc.size() == 2 @@ -113,12 +125,18 @@ class EmbeddedAssociationJoinSpec extends Specification implements H2TestPropert } @JdbcRepository(dialect = Dialect.H2) -interface MainEntityRepository extends CrudRepository { +interface MainEntityRepository extends CrudRepository, JpaSpecificationExecutor { @Join(value = "assoc", type = Join.Type.FETCH) @Join(value = "em.assoc", type = Join.Type.FETCH) @Override Optional findById(Long aLong) + + @JoinSpecifications(@Join(value = "child", type = Join.Type.LEFT_FETCH)) + Page findAll(Pageable pageable) + + @JoinSpecifications(@Join(value = "child", type = Join.Type.LEFT_FETCH)) + Page findAllByCriteria(PredicateSpecification spec, Pageable pageable) } @JdbcRepository(dialect = Dialect.H2) @@ -199,4 +217,4 @@ class MainEntityAssociation { @GeneratedValue Long id String name -} \ No newline at end of file +} diff --git a/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/OrderBySpec.groovy b/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/OrderBySpec.groovy index 49d772c35ac..334295830e6 100644 --- a/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/OrderBySpec.groovy +++ b/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/OrderBySpec.groovy @@ -18,8 +18,50 @@ package io.micronaut.data.processor.visitors import io.micronaut.data.annotation.Query import io.micronaut.data.tck.entities.Company +import static io.micronaut.data.processor.visitors.TestUtils.getQuery + class OrderBySpec extends AbstractDataSpec { + void "test sort embedded"() { + given: + def repository = buildRepository('test.TestRepository', ''' +import io.micronaut.data.annotation.Repository; +import io.micronaut.data.repository.GenericRepository; +import io.micronaut.data.tck.entities.Shipment; +import io.micronaut.data.tck.entities.ShipmentDto; +import io.micronaut.data.tck.entities.ShipmentId; +@Repository +interface TestRepository extends GenericRepository { + List findAllOrderByShipmentIdCity(); +} +''') + when: + def queryFindByText = getQuery(repository.getRequiredMethod("findAllOrderByShipmentIdCity")) + then: + queryFindByText == 'SELECT shipment_ FROM io.micronaut.data.tck.entities.Shipment AS shipment_ ORDER BY shipment_.shipmentId.city ASC' + } + + void "test sort embedded JDBC"() { + given: + def repository = buildRepository('test.TestRepository', ''' +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.GenericRepository; +import io.micronaut.data.tck.entities.Shipment; +import io.micronaut.data.tck.entities.ShipmentDto; +import io.micronaut.data.tck.entities.ShipmentId; + +@JdbcRepository(dialect = Dialect.POSTGRES) +interface TestRepository extends GenericRepository { + List findAllOrderByShipmentIdCity(); +} +''') + when: + def queryFindByText = getQuery(repository.getRequiredMethod("findAllOrderByShipmentIdCity")) + then: + queryFindByText == 'SELECT shipment_."sp_country",shipment_."sp_city",shipment_."field" FROM "Shipment1" shipment_ ORDER BY shipment_."sp_city" ASC' + } + void "test order by date created"() { given: def repository = buildRepository('test.MyInterface', """ 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 82b2b521081..1334f2e3d2c 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 @@ -62,6 +62,7 @@ import jakarta.persistence.criteria.Selection; import java.util.ArrayList; +import java.util.Iterator; import java.util.Collection; import java.util.Comparator; import java.util.List; @@ -376,7 +377,16 @@ private CriteriaQuery createSelectIdsCriteriaQuery(MethodInvocationCo selection.add(getIdExpression(root)); // We need to select all ordered properties from ORDER BY for DISTINCT to work properly for (Sort.Order order : sort.getOrderBy()) { - selection.add(root.get(order.getProperty())); + Path path = null; + for (Iterator iterator = StringUtils.splitOmitEmptyStrings(order.getProperty(), '.').iterator(); iterator.hasNext(); ) { + String next = iterator.next(); + if (iterator.hasNext()) { + path = root.join(next); + } else { + path = root.get(next); + } + } + selection.add(path); } criteriaQuery.multiselect(selection).distinct(true); if (specification != null) { @@ -577,8 +587,13 @@ private List getOrders(Sort sort, Root root, CriteriaBuilder cb) { List orders = new ArrayList<>(); for (Sort.Order order : sort.getOrderBy()) { Path path = root; - for (String property : StringUtils.splitOmitEmptyStrings(order.getProperty(), '.')) { - path = path.get(property); + for (Iterator iterator = StringUtils.splitOmitEmptyStrings(order.getProperty(), '.').iterator(); iterator.hasNext(); ) { + String next = iterator.next(); + if (iterator.hasNext()) { + path = root.join(next); + } else { + path = root.get(next); + } } Expression expression = order.isIgnoreCase() ? cb.lower(path.type().as(String.class)) : path; orders.add(order.isAscending() ? cb.asc(expression) : cb.desc(expression));