You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/04/09 14:52:48 UTC

(grails-core) 07/40: test(h7): expand HibernateCriteriaBuilder coverage across both specs (168 tests total)

This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 3dd58d5f9976ceef8302dfee87d4524264bbeb41
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Apr 7 14:16:23 2026 -0500

    test(h7): expand HibernateCriteriaBuilder coverage across both specs (168 tests total)
    
    HibernateCriteriaBuilderDirectSpec — added 48 new tests covering:
    - eqAll/gtAll/ltAll/geAll/leAll (both QueryableCriteria and Closure variants)
    - gtSome/geSome/ltSome/leSome (both variants)
    - in/inList/notIn via QueryableCriteria and Closure subqueries
    - exists/notExists with QueryableCriteria
    - and/not Closure combinators
    - count(String), count(String,String) void projection methods
    - Alias projections: countDistinct, groupProperty, max, min, sum with alias
    - convertFromInt (LEFT=1, RIGHT=2, INNER=default)
    - isParticipate() getter
    - fetchMode with FetchMode.SELECT branch
    - getClassForAssociationType (PluralAttribute and SingularAttribute)
    - throwRuntimeException disconnects session and rethrows
    
    HibernateCriteriaBuilderSpec — added 'not combinator excludes matching rows' DSL test
    
    Co-authored-by: Copilot <22...@users.noreply.github.com>
---
 JACOCO_COVERAGE_VIOLATIONS.md                      |   2 +-
 .../orm/HibernateCriteriaBuilderDirectSpec.groovy  | 227 +++++++++++++++++++++
 .../grails/orm/HibernateCriteriaBuilderSpec.groovy |  13 ++
 3 files changed, 241 insertions(+), 1 deletion(-)

diff --git a/JACOCO_COVERAGE_VIOLATIONS.md b/JACOCO_COVERAGE_VIOLATIONS.md
index e67018d32b..915e45f45f 100644
--- a/JACOCO_COVERAGE_VIOLATIONS.md
+++ b/JACOCO_COVERAGE_VIOLATIONS.md
@@ -24,7 +24,7 @@ Generated on: 2026-04-06 16:49:20
 | org.grails.orm.hibernate.cfg.DiscriminatorConfig | 72.22% | X
 | org.grails.orm.hibernate.GrailsHibernateTemplate | 72.43% | R
 | org.grails.orm.hibernate.connections.HibernateConnectionSourceSettingsBuilder | 72.81% | X
-| grails.orm.HibernateCriteriaBuilder | 74.43% |
+| grails.orm.HibernateCriteriaBuilder | 74.43% | X |
 | org.grails.orm.hibernate.support.SoftKey | 74.55% | X
 | org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyCollectionProperty | 75.00% | X
 | org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateMappingFactory | 75.14% | X
diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderDirectSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderDirectSpec.groovy
index ee04d28d14..aa2dd08b48 100644
--- a/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderDirectSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderDirectSpec.groovy
@@ -19,10 +19,12 @@
 
 package grails.orm
 
+import grails.gorm.DetachedCriteria
 import grails.gorm.annotation.Entity
 import grails.gorm.specs.HibernateGormDatastoreSpec
 import jakarta.persistence.criteria.JoinType
 import org.grails.datastore.mapping.query.Query
+import org.grails.datastore.mapping.query.api.Criteria
 import org.hibernate.Session
 import org.grails.orm.hibernate.support.hibernate7.SessionHolder
 import org.springframework.transaction.support.TransactionSynchronizationManager
@@ -672,6 +674,231 @@ class HibernateCriteriaBuilderDirectSpec extends HibernateGormDatastoreSpec {
             nativeSession.close()
         }
     }
+
+    // ── Subquery *All / *Some ─────────────────────────────────────────────────
+
+    def "eqAll(String, Closure) builds a DetachedCriteria and delegates"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('balance') } }
+        expect: builder.eqAll('balance', subquery).is(builder)
+    }
+
+    def "eqAll(String, Closure) closure variant delegates via DetachedCriteria"() {
+        expect: builder.eqAll('balance', { projections { property('balance') } }).is(builder)
+    }
+
+    def "gtAll(String, QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('balance') } }
+        expect: builder.gtAll('balance', subquery).is(builder)
+    }
+
+    def "gtAll(String, Closure) closure variant delegates via DetachedCriteria"() {
+        expect: builder.gtAll('balance', { projections { property('balance') } }).is(builder)
+    }
+
+    def "ltAll(String, QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('balance') } }
+        expect: builder.ltAll('balance', subquery).is(builder)
+    }
+
+    def "ltAll(String, Closure) closure variant delegates via DetachedCriteria"() {
+        expect: builder.ltAll('balance', { projections { property('balance') } }).is(builder)
+    }
+
+    def "geAll(String, QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('balance') } }
+        expect: builder.geAll('balance', subquery).is(builder)
+    }
+
+    def "geAll(String, Closure) closure variant delegates via DetachedCriteria"() {
+        expect: builder.geAll('balance', { projections { property('balance') } }).is(builder)
+    }
+
+    def "leAll(String, QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('balance') } }
+        expect: builder.leAll('balance', subquery).is(builder)
+    }
+
+    def "leAll(String, Closure) closure variant delegates via DetachedCriteria"() {
+        expect: builder.leAll('balance', { projections { property('balance') } }).is(builder)
+    }
+
+    def "gtSome(String, QueryableCriteria) is a no-op and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('balance') } }
+        expect: builder.gtSome('balance', subquery).is(builder)
+    }
+
+    def "gtSome(String, Closure) closure variant delegates via DetachedCriteria"() {
+        expect: builder.gtSome('balance', { projections { property('balance') } }).is(builder)
+    }
+
+    def "geSome(String, QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('balance') } }
+        expect: builder.geSome('balance', subquery).is(builder)
+    }
+
+    def "geSome(String, Closure) closure variant delegates via DetachedCriteria"() {
+        expect: builder.geSome('balance', { projections { property('balance') } }).is(builder)
+    }
+
+    def "ltSome(String, QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('balance') } }
+        expect: builder.ltSome('balance', subquery).is(builder)
+    }
+
+    def "ltSome(String, Closure) closure variant delegates via DetachedCriteria"() {
+        expect: builder.ltSome('balance', { projections { property('balance') } }).is(builder)
+    }
+
+    def "leSome(String, QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('balance') } }
+        expect: builder.leSome('balance', subquery).is(builder)
+    }
+
+    def "leSome(String, Closure) closure variant delegates via DetachedCriteria"() {
+        expect: builder.leSome('balance', { projections { property('balance') } }).is(builder)
+    }
+
+    // ── Subquery in / notIn / exists ─────────────────────────────────────────
+
+    def "in(String, QueryableCriteria) delegates to inList and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('id') } }
+        expect: builder.in('id', subquery).is(builder)
+    }
+
+    def "in(String, Closure) closure variant delegates and returns this"() {
+        expect: builder.in('id', { projections { property('id') } }).is(builder)
+    }
+
+    def "inList(String, QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('id') } }
+        expect: builder.inList('id', subquery).is(builder)
+    }
+
+    def "inList(String, Closure) closure variant delegates and returns this"() {
+        expect: builder.inList('id', { projections { property('id') } }).is(builder)
+    }
+
+    def "notIn(String, QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('id') } }
+        expect: builder.notIn('id', subquery).is(builder)
+    }
+
+    def "notIn(String, Closure) closure variant delegates and returns this"() {
+        expect: builder.notIn('id', { projections { property('id') } }).is(builder)
+    }
+
+    def "exists(QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('id') } }
+        expect: builder.exists(subquery).is(builder)
+    }
+
+    def "notExists(QueryableCriteria) delegates and returns this"() {
+        given:
+        def subquery = new DetachedCriteria<>(DirectAccount).build { projections { property('id') } }
+        expect: builder.notExists(subquery).is(builder)
+    }
+
+    // ── Logical combinators ───────────────────────────────────────────────────
+
+    def "and(Closure) delegates and returns this"() {
+        expect: builder.and({ eq('firstName', 'Alice') }).is(builder)
+    }
+
+    def "not(Closure) delegates and returns this"() {
+        expect: builder.not({ eq('firstName', 'Alice') }).is(builder)
+    }
+
+    // ── Projection aliases ────────────────────────────────────────────────────
+
+    def "count(String) adds a count projection without throwing"() {
+        when: builder.count('balance')
+        then: noExceptionThrown()
+    }
+
+    def "count(String, String) adds a count projection with alias without throwing"() {
+        when: builder.count('balance', 'balanceCount')
+        then: noExceptionThrown()
+    }
+
+    def "countDistinct(String, String) adds projection with alias and returns this"() {
+        expect: builder.countDistinct('firstName', 'nameCount').is(builder)
+    }
+
+    def "groupProperty(String, String) adds projection with alias and returns this"() {
+        expect: builder.groupProperty('lastName', 'ln').is(builder)
+    }
+
+    def "max(String, String) adds projection with alias and returns this"() {
+        expect: builder.max('balance', 'maxBal').is(builder)
+    }
+
+    def "min(String, String) adds projection with alias and returns this"() {
+        expect: builder.min('balance', 'minBal').is(builder)
+    }
+
+    def "sum(String, String) adds projection with alias and returns this"() {
+        expect: builder.sum('balance', 'totalBal').is(builder)
+    }
+
+    // ── Utilities ────────────────────────────────────────────────────────────
+
+    def "convertFromInt 1 maps to LEFT join"() {
+        expect: builder.convertFromInt(1) == JoinType.LEFT
+    }
+
+    def "convertFromInt 2 maps to RIGHT join"() {
+        expect: builder.convertFromInt(2) == JoinType.RIGHT
+    }
+
+    def "convertFromInt 0 defaults to INNER join"() {
+        expect: builder.convertFromInt(0) == JoinType.INNER
+    }
+
+    def "isParticipate returns true when session was already bound at construction time"() {
+        expect: builder.isParticipate()
+    }
+
+    def "fetchMode(String, SELECT) applies select strategy without throwing"() {
+        when: builder.fetchMode('transactions', org.hibernate.FetchMode.SELECT)
+        then: noExceptionThrown()
+    }
+
+    def "getClassForAssociationType with PluralAttribute returns element java type"() {
+        given:
+        def attr = manager.hibernateDatastore.sessionFactory.metamodel.entity(DirectAccount).getAttribute('transactions')
+
+        expect:
+        builder.getClassForAssociationType(attr) == DirectTransaction
+    }
+
+    def "getClassForAssociationType with SingularAttribute returns attribute java type"() {
+        given:
+        def attr = manager.hibernateDatastore.sessionFactory.metamodel.entity(DirectAccount).getAttribute('firstName')
+
+        expect:
+        builder.getClassForAssociationType(attr) == String
+    }
+
+    void "throwRuntimeException disconnects session and re-throws"() {
+        when:
+        builder.throwRuntimeException(new RuntimeException("test error"))
+
+        then:
+        thrown(RuntimeException)
+    }
 }
 
 @Entity
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 ee459a15ae..36718fd045 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
@@ -543,6 +543,19 @@ class HibernateCriteriaBuilderSpec extends HibernateGormDatastoreSpec {
         result != null
         result.firstName == "Fred"
     }
+
+    void "not combinator excludes matching rows"() {
+        when:
+        def results = c.list {
+            not {
+                isNull('branch')
+            }
+        }
+
+        then:
+        results.size() == 4
+        results.every { it.branch != null }
+    }
 }
 
 @Entity