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 5bb1a644aab0e1454b4a7f829a2b7528feb7554b Author: Walter Duque de Estrada <[email protected]> AuthorDate: Thu Feb 19 13:49:41 2026 -0600 Update HibernateQuerySpec for parity with HibernateCriteriaBuilderSpec and improve PredicateGenerator --- .../grails/orm/HibernateCriteriaBuilder.java | 92 +++++++++++++--------- .../orm/hibernate/query/PredicateGenerator.java | 22 +----- .../specs/hibernatequery/HibernateQuerySpec.groovy | 84 +++++++++++++++++++- .../grails/orm/HibernateCriteriaBuilderSpec.groovy | 83 ++++++++++++++++++- 4 files changed, 221 insertions(+), 60 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java b/grails-data-hibernate7/core/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java index c6d8ed3df7..3a3b6b14c1 100644 --- a/grails-data-hibernate7/core/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java +++ b/grails-data-hibernate7/core/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java @@ -108,42 +108,6 @@ public class HibernateCriteriaBuilder extends GroovyObjectSupport implements Bui * Define constants which may be used inside of criteria queries * to refer to standard Hibernate Type instances. */ - public static final BasicTypeReference<Boolean> BOOLEAN = StandardBasicTypes.BOOLEAN; - public static final BasicTypeReference<Boolean> YES_NO = StandardBasicTypes.YES_NO; - public static final BasicTypeReference<Byte> BYTE = StandardBasicTypes.BYTE; - public static final BasicTypeReference<Character> CHARACTER = StandardBasicTypes.CHARACTER; - public static final BasicTypeReference<Short> SHORT = StandardBasicTypes.SHORT; - public static final BasicTypeReference<Integer> INTEGER = StandardBasicTypes.INTEGER; - public static final BasicTypeReference<Long> LONG = StandardBasicTypes.LONG; - public static final BasicTypeReference<Float> FLOAT = StandardBasicTypes.FLOAT; - public static final BasicTypeReference<Double> DOUBLE = StandardBasicTypes.DOUBLE; - public static final BasicTypeReference<BigDecimal> BIG_DECIMAL = StandardBasicTypes.BIG_DECIMAL; - public static final BasicTypeReference<BigInteger> BIG_INTEGER = StandardBasicTypes.BIG_INTEGER; - public static final BasicTypeReference<String> STRING = StandardBasicTypes.STRING; - public static final BasicTypeReference<Boolean> NUMERIC_BOOLEAN = StandardBasicTypes.NUMERIC_BOOLEAN; - public static final BasicTypeReference<Boolean> TRUE_FALSE = StandardBasicTypes.TRUE_FALSE; - public static final BasicTypeReference<java.net.URL> URL = StandardBasicTypes.URL; - public static final BasicTypeReference<Date> TIME = StandardBasicTypes.TIME; - public static final BasicTypeReference<Date> DATE = StandardBasicTypes.DATE; - public static final BasicTypeReference<Date> TIMESTAMP = StandardBasicTypes.TIMESTAMP; - public static final BasicTypeReference<Calendar> CALENDAR = StandardBasicTypes.CALENDAR; - public static final BasicTypeReference<Calendar> CALENDAR_DATE = StandardBasicTypes.CALENDAR_DATE; - public static final BasicTypeReference<Class> CLASS = StandardBasicTypes.CLASS; - public static final BasicTypeReference<Locale> LOCALE = StandardBasicTypes.LOCALE; - public static final BasicTypeReference<Currency> CURRENCY = StandardBasicTypes.CURRENCY; - public static final BasicTypeReference<TimeZone> TIMEZONE = StandardBasicTypes.TIMEZONE; - public static final BasicTypeReference<UUID> UUID_BINARY = StandardBasicTypes.UUID_BINARY; - public static final BasicTypeReference<UUID> UUID_CHAR = StandardBasicTypes.UUID_CHAR; - public static final BasicTypeReference<byte[]> BINARY = StandardBasicTypes.BINARY; - public static final BasicTypeReference<byte[]> IMAGE = StandardBasicTypes.IMAGE; - public static final BasicTypeReference<Blob> BLOB = StandardBasicTypes.BLOB; - public static final BasicTypeReference<byte[]> MATERIALIZED_BLOB = StandardBasicTypes.MATERIALIZED_BLOB; - public static final BasicTypeReference<char[]> CHAR_ARRAY = StandardBasicTypes.CHAR_ARRAY; - public static final BasicTypeReference<Character[]> CHARACTER_ARRAY = StandardBasicTypes.CHARACTER_ARRAY; - public static final BasicTypeReference<String> TEXT = StandardBasicTypes.TEXT; - public static final BasicTypeReference<Clob> CLOB = StandardBasicTypes.CLOB; - public static final BasicTypeReference<String> MATERIALIZED_CLOB = StandardBasicTypes.MATERIALIZED_CLOB; - public static final BasicTypeReference<Serializable> SERIALIZABLE = StandardBasicTypes.SERIALIZABLE; public static final String AND = "and"; // builder public static final String IS_NULL = "isNull"; // builder @@ -1379,6 +1343,62 @@ public class HibernateCriteriaBuilder extends GroovyObjectSupport implements Bui else { hibernateQuery.isNotEmpty(propertyName); } + return name; + } + } + else if (args.length >= 2 && args[0] instanceof String propertyName) { + propertyName = calculatePropertyName(propertyName); + switch (name) { + case RLIKE: + return rlike(propertyName, args[1]); + case BETWEEN: + if (args.length >= 3) { + return between(propertyName, args[1], args[2]); + } + break; + case EQUALS: + if (args.length == 3 && args[2] instanceof Map) { + return eq(propertyName, args[1], (Map) args[2]); + } + return eq(propertyName, args[1]); + case EQUALS_PROPERTY: + return eqProperty(propertyName, args[1].toString()); + case GREATER_THAN: + return gt(propertyName, args[1]); + case GREATER_THAN_PROPERTY: + return gtProperty(propertyName, args[1].toString()); + case GREATER_THAN_OR_EQUAL: + return ge(propertyName, args[1]); + case GREATER_THAN_OR_EQUAL_PROPERTY: + return geProperty(propertyName, args[1].toString()); + case ILIKE: + return ilike(propertyName, args[1]); + case IN: + if (args[1] instanceof Collection) { + return in(propertyName, (Collection) args[1]); + } else if (args[1] instanceof Object[]) { + return in(propertyName, (Object[]) args[1]); + } + break; + case LESS_THAN: + return lt(propertyName, args[1]); + case LESS_THAN_PROPERTY: + return ltProperty(propertyName, args[1].toString()); + case LESS_THAN_OR_EQUAL: + return le(propertyName, args[1]); + case LESS_THAN_OR_EQUAL_PROPERTY: + return leProperty(propertyName, args[1].toString()); + case LIKE: + return like(propertyName, args[1]); + case NOT_EQUAL: + return ne(propertyName, args[1]); + case NOT_EQUAL_PROPERTY: + return neProperty(propertyName, args[1].toString()); + case SIZE_EQUALS: + if (args[1] instanceof Number) { + return sizeEq(propertyName, ((Number) args[1]).intValue()); + } + break; } } throw new MissingMethodException(name, getClass(), args); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java index 12ff7965ff..08de63641b 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java @@ -108,27 +108,7 @@ public class PredicateGenerator { } else if (criterion instanceof Query.SizeLessThanEquals c) { return cb.le(cb.size(fullyQualifiedPath), getNumericValue(c)); } else if (criterion instanceof Query.Between c) { - if (c.getFrom() instanceof String && c.getTo() instanceof String) { - return cb.between(fullyQualifiedPath, (String) c.getFrom(), (String) c.getTo()); - } else if (c.getFrom() instanceof Short && c.getTo() instanceof Short) { - return cb.between(fullyQualifiedPath, (Short) c.getFrom(), (Short) c.getTo()); - } else if (c.getFrom() instanceof Integer && c.getTo() instanceof Integer) { - return cb.between(fullyQualifiedPath, (Integer) c.getFrom(), (Integer) c.getTo()); - } else if (c.getFrom() instanceof Long && c.getTo() instanceof Long) { - return cb.between(fullyQualifiedPath, (Long) c.getFrom(), (Long) c.getTo()); - } else if (c.getFrom() instanceof Date && c.getTo() instanceof Date) { - return cb.between(fullyQualifiedPath, (Date) c.getFrom(), (Date) c.getTo()); - } else if (c.getFrom() instanceof Instant && c.getTo() instanceof Instant) { - return cb.between(fullyQualifiedPath, (Instant) c.getFrom(), (Instant) c.getTo()); - } else if (c.getFrom() instanceof LocalDate && c.getTo() instanceof LocalDate) { - return cb.between(fullyQualifiedPath, (LocalDate) c.getFrom(), (LocalDate) c.getTo()); - } else if (c.getFrom() instanceof LocalDateTime && c.getTo() instanceof LocalDateTime) { - return cb.between(fullyQualifiedPath, (LocalDateTime) c.getFrom(), (LocalDateTime) c.getTo()); - } else if (c.getFrom() instanceof OffsetDateTime && c.getTo() instanceof OffsetDateTime) { - return cb.between(fullyQualifiedPath, (OffsetDateTime) c.getFrom(), (OffsetDateTime) c.getTo()); - } else if (c.getFrom() instanceof ZonedDateTime && c.getTo() instanceof ZonedDateTime) { - return cb.between(fullyQualifiedPath, (ZonedDateTime) c.getFrom(), (ZonedDateTime) c.getTo()); - } + return cb.between(fullyQualifiedPath, (Comparable) c.getFrom(), (Comparable) c.getTo()); } else if (criterion instanceof Query.ILike c) { return cb.ilike(fullyQualifiedPath, c.getValue().toString()); } else if (criterion instanceof Query.RLike c) { diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy index b4b7059c35..d21c8580ec 100644 --- a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy @@ -40,7 +40,7 @@ class HibernateQuerySpec extends HibernateGormDatastoreSpec { } def setupSpec() { - manager.addAllDomainClasses([Person, Pet, Face,EagerOwner]) + manager.addAllDomainClasses([Person, Pet, Face, EagerOwner, CommonTypes, BigDecimalEntity]) } def equals() { @@ -524,6 +524,46 @@ class HibernateQuerySpec extends HibernateGormDatastoreSpec { oldBob == newBob } + def betweenBigDecimal() { + given: + HibernateDatastore hibernateDatastore = manager.hibernateDatastore + AbstractHibernateSession session = hibernateDatastore.connect() as AbstractHibernateSession + HibernateQuery query = new HibernateQuery(session, hibernateDatastore.getMappingContext().getPersistentEntity(BigDecimalEntity.typeName)) + new BigDecimalEntity(amount: 10.5G).save(flush: true, failOnError: true) + new BigDecimalEntity(amount: 20.5G).save(flush: true, failOnError: true) + new BigDecimalEntity(amount: 30.5G).save(flush: true, failOnError: true) + + query.between("amount", 15.0G, 25.0G) + + when: + def results = query.list() + + then: + results.size() == 1 + results[0].amount == 20.5G + } + + def inListArray() { + new Person(firstName: "Fred", lastName: "Rogers", age: 52).save(flush: true) + given: + hibernateQuery.in("age", [50, 52]) + when: + def results = hibernateQuery.list() + then: + results.size() == 2 + results*.firstName.sort() == ["Bob", "Fred"] + } + + def countDistinct() { + new Person(firstName: "Bob", lastName: "The Builder", age: 25).save(flush: true) + given: + hibernateQuery.projections().countDistinct("firstName") + when: + def count = hibernateQuery.singleResult() + then: + count == 1 // Both are "Bob" + } + def joinWithProjection() { given: oldBob.addToPets(new Pet(name:"Lucky")).save(flush:true) @@ -657,6 +697,40 @@ class HibernateQuerySpec extends HibernateGormDatastoreSpec { age == 51 } + def sumBigDecimal() { + given: + HibernateDatastore hibernateDatastore = manager.hibernateDatastore + AbstractHibernateSession session = hibernateDatastore.connect() as AbstractHibernateSession + HibernateQuery query = new HibernateQuery(session, hibernateDatastore.getMappingContext().getPersistentEntity(BigDecimalEntity.typeName)) + new BigDecimalEntity(amount: 100.0G).save(flush: true, failOnError: true) + new BigDecimalEntity(amount: 200.0G).save(flush: true, failOnError: true) + + query.projections().sum("amount") + + when: + def sum = query.singleResult() + + then: + sum == 300.0G + } + + def avgBigDecimal() { + given: + HibernateDatastore hibernateDatastore = manager.hibernateDatastore + AbstractHibernateSession session = hibernateDatastore.connect() as AbstractHibernateSession + HibernateQuery query = new HibernateQuery(session, hibernateDatastore.getMappingContext().getPersistentEntity(BigDecimalEntity.typeName)) + new BigDecimalEntity(amount: 100.0G).save(flush: true, failOnError: true) + new BigDecimalEntity(amount: 200.0G).save(flush: true, failOnError: true) + + query.projections().avg("amount") + + when: + def avg = query.singleResult() + + then: + avg == 150.0G + } + def groupByLastNameAverageAge() { def fred = new Person(firstName: "Fred", lastName: "Rogers", age: 52) fred.save(flush: true) @@ -739,3 +813,11 @@ class HibernateQuerySpec extends HibernateGormDatastoreSpec { } + [email protected] +class BigDecimalEntity implements Serializable { + Long id + Long version + BigDecimal amount +} + diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderSpec.groovy index d8ea1cf885..3e63b316fb 100644 --- a/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderSpec.groovy @@ -80,6 +80,53 @@ class HibernateCriteriaBuilderSpec extends HibernateGormDatastoreSpec { results[0].firstName == "Fred" } + void "test rlike criteria"() { + when: + def results = builder.list { + rlike("firstName", "^F.*") + } + + then: + results.size() == 1 + results[0].firstName == "Fred" + } + + void "test between criteria"() { + when: + def results = builder.list { + between("balance", BigDecimal.valueOf(100), BigDecimal.valueOf(300)) + } + + then: + results.size() == 2 + results*.firstName.sort() == ["Fred", "Wilma"] + } + + void "test sizeEq criteria"() { + when: + def results = builder.list { + sizeEq("transactions", 2) + } + + then: + results.size() == 1 + results[0].firstName == "Fred" + } + + void "test isEmpty and isNotEmpty criteria"() { + when: + def emptyResults = builder.list { + isEmpty("transactions") + } + def notEmptyResults = builder.list { + isNotEmpty("transactions") + } + + then: + emptyResults.size() == 3 // Wilma, Pebbles, Bam-Bam + notEmptyResults.size() == 2 // Fred, Barney + } + void "test isNull criteria"() { when: def results = builder.list { @@ -168,11 +215,43 @@ class HibernateCriteriaBuilderSpec extends HibernateGormDatastoreSpec { when: def results = builder.list { geProperty("balance", "balance") // always true, validates path + eqProperty("firstName", "firstName") + neProperty("firstName", "lastName") + gtProperty("balance", "balance") // always false for same property order("balance", "desc") } then: - results[0].firstName == "Pebbles" - results[-1].firstName == "Bam-Bam" + results.size() == 0 // because gtProperty("balance", "balance") is false + + when: + results = builder.list { + leProperty("balance", "balance") + ltProperty("balance", "balance") + } + then: + results.size() == 0 + } + + void "test nested criteria with aliases"() { + when: + def results = builder.list { + transactions { + eq("amount", BigDecimal.valueOf(50)) + } + } + then: + results.size() == 1 + results[0].firstName == "Barney" + + when: + results = builder.list { + transactions { + between("amount", BigDecimal.valueOf(15), BigDecimal.valueOf(25)) + } + } + then: + results.size() == 1 + results[0].firstName == "Fred" } void "test projections countDistinct groupProperty min max"() {
