This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch 8.0.x-hibernate7 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit ace439b0d069007898bda826e074a89fa3b07ba2 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sun Feb 8 17:00:14 2026 -0600 refactored CollectionSecondPassBinder --- .../cfg/domainbinding/OrderByClauseBuilder.java | 119 +++++++++++++++++++++ .../secondpass/CollectionSecondPassBinder.java | 108 +------------------ .../domainbinding/OrderByClauseBuilderSpec.groovy | 103 ++++++++++++++++++ 3 files changed, 226 insertions(+), 104 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/OrderByClauseBuilder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/OrderByClauseBuilder.java new file mode 100644 index 0000000000..7a81e42fea --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/OrderByClauseBuilder.java @@ -0,0 +1,119 @@ +package org.grails.orm.hibernate.cfg.domainbinding; + +import org.grails.datastore.mapping.model.DatastoreConfigurationException; +import org.hibernate.boot.model.internal.BinderHelper; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Selectable; +import org.hibernate.mapping.SingleTableSubclass; + +import java.util.Iterator; +import java.util.StringTokenizer; + +/** + * Utility class to build SQL order by clauses from HQL-style order by strings. + */ +public class OrderByClauseBuilder { + + @SuppressWarnings("unchecked") + public String buildOrderByClause(String hqlOrderBy, PersistentClass associatedClass, String role, String defaultOrder) { + String orderByString = null; + if (hqlOrderBy != null) { + java.util.List<String> properties = new java.util.ArrayList<>(); + java.util.List<String> ordering = new java.util.ArrayList<>(); + StringBuilder orderByBuffer = new StringBuilder(); + if (hqlOrderBy.length() == 0) { + //order by id + Iterator<?> it = associatedClass.getIdentifier().getSelectables().iterator(); + while (it.hasNext()) { + Selectable col = (Selectable) it.next(); + orderByBuffer.append(col.getText()).append(" asc").append(", "); + } + } + else { + StringTokenizer st = new StringTokenizer(hqlOrderBy, " ,", false); + String currentOrdering = defaultOrder; + //FIXME make this code decent + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (isNonPropertyToken(token)) { + if (currentOrdering != null) { + throw new DatastoreConfigurationException( + "Error while parsing sort clause: " + hqlOrderBy + + " (" + role + ")" + ); + } + currentOrdering = token; + } + else { + //Add ordering of the previous + if (currentOrdering == null) { + //default ordering + ordering.add("asc"); + } + else { + ordering.add(currentOrdering); + currentOrdering = null; + } + properties.add(token); + } + } + ordering.remove(0); //first one is the algorithm starter + // add last one ordering + if (currentOrdering == null) { + //default ordering + ordering.add(defaultOrder); + } + else { + ordering.add(currentOrdering); + currentOrdering = null; + } + int index = 0; + + for (String property : properties) { + Property p = BinderHelper.findPropertyByName(associatedClass, property); + if (p == null) { + throw new DatastoreConfigurationException( + "property from sort clause not found: " + + associatedClass.getEntityName() + "." + property + ); + } + PersistentClass pc = p.getPersistentClass(); + String table; + if (pc == null) { + table = ""; + } + + else if (pc == associatedClass + || (associatedClass instanceof SingleTableSubclass && + pc.getMappedClass().isAssignableFrom(associatedClass.getMappedClass()))) { + table = ""; + } else { + table = pc.getTable().getQuotedName() + "."; + } + + Iterator<?> propertyColumns = p.getSelectables().iterator(); + while (propertyColumns.hasNext()) { + Selectable column = (Selectable) propertyColumns.next(); + orderByBuffer.append(table) + .append(column.getText()) + .append(" ") + .append(ordering.get(index)) + .append(", "); + } + index++; + } + } + orderByString = orderByBuffer.substring(0, orderByBuffer.length() - 2); + } + return orderByString; + } + + private boolean isNonPropertyToken(String token) { + if (" ".equals(token)) return true; + if (",".equals(token)) return true; + if (token.equalsIgnoreCase("desc")) return true; + if (token.equalsIgnoreCase("asc")) return true; + return false; + } +} diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java index e35baa073b..fb414a8f56 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java @@ -16,6 +16,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.CompositeIdentifierToManyToOne import org.grails.orm.hibernate.cfg.domainbinding.DefaultColumnNameFetcher; import org.grails.orm.hibernate.cfg.domainbinding.EnumTypeBinder; import org.grails.orm.hibernate.cfg.domainbinding.ManyToOneBinder; +import org.grails.orm.hibernate.cfg.domainbinding.OrderByClauseBuilder; import org.grails.orm.hibernate.cfg.domainbinding.RootMappingFetcher; import org.grails.orm.hibernate.cfg.domainbinding.SimpleValueBinder; import org.grails.orm.hibernate.cfg.domainbinding.SimpleValueColumnBinder; @@ -47,11 +48,13 @@ public class CollectionSecondPassBinder { private final MetadataBuildingContext metadataBuildingContext; private final PersistentEntityNamingStrategy namingStrategy; private final DefaultColumnNameFetcher defaultColumnNameFetcher; + private final OrderByClauseBuilder orderByClauseBuilder; public CollectionSecondPassBinder(MetadataBuildingContext metadataBuildingContext, PersistentEntityNamingStrategy namingStrategy) { this.metadataBuildingContext = metadataBuildingContext; this.namingStrategy = namingStrategy; this.defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy); + this.orderByClauseBuilder = new OrderByClauseBuilder(); } @@ -80,7 +83,7 @@ public class CollectionSecondPassBinder { associatedClass = (PersistentClass) persistentClasses.get(associatedClassName); if (associatedClass != null) { - collection.setOrderBy(buildOrderByClause(propertyToSortBy.getName(), associatedClass, collection.getRole(), + collection.setOrderBy(orderByClauseBuilder.buildOrderByClause(propertyToSortBy.getName(), associatedClass, collection.getRole(), propConfig.getOrder() != null ? propConfig.getOrder() : "asc")); } } @@ -472,107 +475,4 @@ public class CollectionSecondPassBinder { } return theSet; } - - @SuppressWarnings("unchecked") - private String buildOrderByClause(String hqlOrderBy, PersistentClass associatedClass, String role, String defaultOrder) { - String orderByString = null; - if (hqlOrderBy != null) { - java.util.List<String> properties = new java.util.ArrayList<>(); - java.util.List<String> ordering = new java.util.ArrayList<>(); - StringBuilder orderByBuffer = new StringBuilder(); - if (hqlOrderBy.length() == 0) { - //order by id - Iterator<?> it = associatedClass.getIdentifier().getSelectables().iterator(); - while (it.hasNext()) { - Selectable col = (Selectable) it.next(); - orderByBuffer.append(col.getText()).append(" asc").append(", "); - } - } - else { - StringTokenizer st = new StringTokenizer(hqlOrderBy, " ,", false); - String currentOrdering = defaultOrder; - //FIXME make this code decent - while (st.hasMoreTokens()) { - String token = st.nextToken(); - if (isNonPropertyToken(token)) { - if (currentOrdering != null) { - throw new DatastoreConfigurationException( - "Error while parsing sort clause: " + hqlOrderBy - + " (" + role + ")" - ); - } - currentOrdering = token; - } - else { - //Add ordering of the previous - if (currentOrdering == null) { - //default ordering - ordering.add("asc"); - } - else { - ordering.add(currentOrdering); - currentOrdering = null; - } - properties.add(token); - } - } - ordering.remove(0); //first one is the algorithm starter - // add last one ordering - if (currentOrdering == null) { - //default ordering - ordering.add(defaultOrder); - } - else { - ordering.add(currentOrdering); - currentOrdering = null; - } - int index = 0; - - for (String property : properties) { - Property p = BinderHelper.findPropertyByName(associatedClass, property); - if (p == null) { - throw new DatastoreConfigurationException( - "property from sort clause not found: " - + associatedClass.getEntityName() + "." + property - ); - } - PersistentClass pc = p.getPersistentClass(); - String table; - if (pc == null) { - table = ""; - } - - else if (pc == associatedClass - || (associatedClass instanceof SingleTableSubclass && - pc.getMappedClass().isAssignableFrom(associatedClass.getMappedClass()))) { - table = ""; - } else { - table = pc.getTable().getQuotedName() + "."; - } - - Iterator<?> propertyColumns = p.getSelectables().iterator(); - while (propertyColumns.hasNext()) { - Selectable column = (Selectable) propertyColumns.next(); - orderByBuffer.append(table) - .append(column.getText()) - .append(" ") - .append(ordering.get(index)) - .append(", "); - } - index++; - } - } - orderByString = orderByBuffer.substring(0, orderByBuffer.length() - 2); - } - return orderByString; - } - - private boolean isNonPropertyToken(String token) { - if (" ".equals(token)) return true; - if (",".equals(token)) return true; - if (token.equalsIgnoreCase("desc")) return true; - if (token.equalsIgnoreCase("asc")) return true; - return false; - } - } diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/OrderByClauseBuilderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/OrderByClauseBuilderSpec.groovy new file mode 100644 index 0000000000..9a0200fd76 --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/OrderByClauseBuilderSpec.groovy @@ -0,0 +1,103 @@ +package org.grails.orm.hibernate.cfg.domainbinding + +import grails.gorm.annotation.Entity +import grails.gorm.specs.HibernateGormDatastoreSpec +import org.hibernate.mapping.PersistentClass +import spock.lang.Subject + +class OrderByClauseBuilderSpec extends HibernateGormDatastoreSpec { + + @Subject + OrderByClauseBuilder builder = new OrderByClauseBuilder() + + void setupSpec() { + manager.addAllDomainClasses([OrderTest, SubOrderTest]) + } + + void "test buildOrderByClause with empty string (default to id)"() { + given: + PersistentClass pc = datastore.metadata.getEntityBinding(OrderTest.name) + + when: + String result = builder.buildOrderByClause("", pc, "role", "asc") + + then: + result == "id asc" + } + + void "test buildOrderByClause with single property"() { + given: + PersistentClass pc = datastore.metadata.getEntityBinding(OrderTest.name) + + when: + String result = builder.buildOrderByClause("name", pc, "role", "asc") + + then: + result == "name asc" + } + + void "test buildOrderByClause with property and explicit order"() { + given: + PersistentClass pc = datastore.metadata.getEntityBinding(OrderTest.name) + + when: + String result = builder.buildOrderByClause("name desc", pc, "role", "asc") + + then: + result == "name desc" + } + + void "test buildOrderByClause with multiple properties"() { + given: + PersistentClass pc = datastore.metadata.getEntityBinding(OrderTest.name) + + when: + String result = builder.buildOrderByClause("name, age desc", pc, "role", "asc") + + then: + result == "name asc, age desc" + } + + void "test buildOrderByClause with mapped column name"() { + given: + PersistentClass pc = datastore.metadata.getEntityBinding(OrderTest.name) + + when: + String result = builder.buildOrderByClause("other", pc, "role", "asc") + + then: + result == "other_column asc" + } + + void "test buildOrderByClause with table prefix for inherited property"() { + given: + PersistentClass pc = datastore.metadata.getEntityBinding(SubOrderTest.name) + + when: + // 'name' is in the base table 'order_test', 'subProperty' is in 'sub_order_test' + String result = builder.buildOrderByClause("name, subProperty", pc, "role", "asc") + + then: + // Hibernate 7 mapping might use different table qualification depending on strategy. + // Assuming joined-subclass or TPH. + // If TPH (default GORM), table prefix might be empty if it's the same table. + result.contains("name asc") + result.contains("sub_property asc") + } +} + +@Entity +class OrderTest { + String name + Integer age + String other + + static mapping = { + other column: 'other_column' + } +} + +@Entity +class SubOrderTest extends OrderTest { + String subProperty +}
