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

borinquenkid pushed a commit to branch merge-hibernate6
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 83be4126d9c0b81df0572e9cdc49b19007db1df6
Author: Walter Duque de Estrada <wbdu...@mac.com>
AuthorDate: Thu Aug 7 21:36:32 2025 -0500

    Size queries
---
 .../hibernate/query/AbstractHibernateQuery.java    |  47 +--
 .../orm/hibernate/query/PredicateGenerator.java    |  40 ++-
 .../CascadeBehaviorFetcherSpec.groovy              |   8 +
 .../testing/tck/domains/Child_BT_Default_P.groovy  |   9 +
 .../testing/tck/domains/Owner_Default_Bi_P.groovy  |  10 +
 .../data/testing/tck/tests/SizeQuerySpec.groovy    | 373 +++++++--------------
 6 files changed, 180 insertions(+), 307 deletions(-)

diff --git 
a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java
 
b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java
index 64777e5d6b..c5db1527d5 100644
--- 
a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java
+++ 
b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java
@@ -42,6 +42,8 @@ import org.hibernate.query.criteria.HibernateCriteriaBuilder;
 import org.hibernate.query.criteria.JpaCriteriaQuery;
 import org.hibernate.query.criteria.JpaExpression;
 import org.hibernate.transform.ResultTransformer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.core.convert.ConversionService;
 import org.springframework.core.convert.support.DefaultConversionService;
 import org.springframework.dao.InvalidDataAccessApiUsageException;
@@ -63,9 +65,9 @@ import java.util.function.Predicate;
  * @since 1.0
  */
 @SuppressWarnings("rawtypes")
-@Slf4j
+//@Slf4j
 public abstract class AbstractHibernateQuery extends Query {
-
+    private static final Logger LOG = 
LoggerFactory.getLogger(AbstractHibernateQuery.class);
     public static final String SIZE_CONSTRAINT_PREFIX = "Size";
 
     protected static final String ALIAS = "_alias";
@@ -166,6 +168,10 @@ public abstract class AbstractHibernateQuery extends Query 
{
         detachedCriteria.add(criterion);
     }
 
+    public void add(DetachedCriteria<?> detachedCriteria) {
+        detachedCriteria.add(new Conjunction(detachedCriteria.getCriteria()));
+    }
+
 
     /**
      * This is called for ORS and is only used by DymamicFinder
@@ -196,11 +202,7 @@ public abstract class AbstractHibernateQuery extends Query 
{
 
     @Override
     public Query gt(String property, Object value) {
-        if(value instanceof Number number) {
-            detachedCriteria.gt(property, number);
-        } else{
-            throw new ConfigurationException("gte only uses numbers");
-        }
+        detachedCriteria.gt(property, value);
         return this;
     }
 
@@ -276,52 +278,31 @@ public abstract class AbstractHibernateQuery extends 
Query {
 
     @Override
     public Query ge(String property, Object value) {
-        if(value instanceof Number number) {
-            detachedCriteria.ge(property, number);
-        } else{
-            throw new ConfigurationException("gte only uses numbers");
-        }
+        detachedCriteria.ge(property, value);
         return this;
     }
 
     @Override
     public Query le(String property, Object value) {
-        if(value instanceof Number number) {
-            detachedCriteria.le(property, number);
-        } else{
-            throw new ConfigurationException("gte only uses numbers");
-        }
+        detachedCriteria.le(property, value);
         return this;
     }
 
     @Override
     public Query gte(String property, Object value) {
-        if(value instanceof Number number) {
-            detachedCriteria.gte(property, number);
-        } else{
-            throw new ConfigurationException("gte only uses numbers");
-        }
-
+        detachedCriteria.gte(property, value);
         return this;
     }
 
     @Override
     public Query lte(String property, Object value) {
-        if(value instanceof Number number) {
-            detachedCriteria.lte(property, number);
-        } else{
-            throw new ConfigurationException("gte only uses numbers");
-        }
+        detachedCriteria.lte(property, value);
         return this;
     }
 
     @Override
     public Query lt(String property, Object value) {
-        if(value instanceof Number number) {
-            detachedCriteria.lt(property, number);
-        } else{
-            throw new ConfigurationException("gte only uses numbers");
-        }
+        detachedCriteria.lt(property, value);
         return this;
     }
 
diff --git 
a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
 
b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
index 837dea451a..14380d6c32 100644
--- 
a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
+++ 
b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
@@ -11,17 +11,15 @@ import jakarta.persistence.criteria.Predicate;
 import jakarta.persistence.criteria.Root;
 import jakarta.persistence.criteria.Subquery;
 import org.grails.datastore.gorm.query.criteria.DetachedAssociationCriteria;
+import org.grails.datastore.mapping.core.exceptions.ConfigurationException;
 import org.grails.datastore.mapping.model.PersistentEntity;
 import org.grails.datastore.mapping.model.types.Association;
 import org.grails.datastore.mapping.query.Query;
 import org.grails.datastore.mapping.query.api.QueryableCriteria;
 import org.hibernate.query.criteria.HibernateCriteriaBuilder;
 import org.hibernate.query.criteria.JpaInPredicate;
-import org.hibernate.query.criteria.JpaJoin;
-import org.hibernate.query.criteria.JpaPath;
 import org.hibernate.query.criteria.JpaPredicate;
 import org.hibernate.query.sqm.tree.domain.SqmPath;
-import org.hibernate.query.sqm.tree.domain.SqmSetJoin;
 import org.hibernate.query.sqm.tree.predicate.SqmInListPredicate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -104,25 +102,25 @@ public class PredicateGenerator {
                     } else if (criterion instanceof Query.IdEquals c) {
                         return cb.equal(root_.get("id"), c.getValue());
                     } else if (criterion instanceof Query.GreaterThan c) {
-                        return 
cb.gt(fromsByProvider.getFullyQualifiedPath(c.getProperty()), (Number) 
c.getValue());
+                        return 
cb.gt(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
getNumericValue(c));
                     } else if (criterion instanceof Query.GreaterThanEquals c) 
{
-                        return 
cb.ge(fromsByProvider.getFullyQualifiedPath(c.getProperty()), (Number) 
c.getValue());
+                        return 
cb.ge(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
getNumericValue(c));
                     } else if (criterion instanceof Query.LessThan c) {
-                        return 
cb.lt(fromsByProvider.getFullyQualifiedPath(c.getProperty()), (Number) 
c.getValue());
+                        return 
cb.lt(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
getNumericValue(c));
                     } else if (criterion instanceof Query.LessThanEquals c) {
-                        return 
cb.le(fromsByProvider.getFullyQualifiedPath(c.getProperty()), (Number) 
c.getValue());
+                        return 
cb.le(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
getNumericValue(c));
                     } else if (criterion instanceof Query.SizeEquals c) {
                         return 
cb.equal(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
c.getValue());
                     } else if (criterion instanceof Query.SizeNotEquals c) {
                         return 
cb.notEqual(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
c.getValue());
                     } else if (criterion instanceof Query.SizeGreaterThan c) {
-                        return 
cb.gt(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), (Number) 
c.getValue());
+                        return 
cb.gt(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
getNumericValue(c));
                     } else if (criterion instanceof 
Query.SizeGreaterThanEquals c) {
-                        return 
cb.ge(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), (Number) 
c.getValue());
+                        return 
cb.ge(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
getNumericValue(c));
                     } else if (criterion instanceof Query.SizeLessThan c) {
-                        return 
cb.lt(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), (Number) 
c.getValue());
+                        return 
cb.lt(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
getNumericValue(c));
                     } else if (criterion instanceof Query.SizeLessThanEquals 
c) {
-                        return 
cb.le(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), (Number) 
c.getValue());
+                        return 
cb.le(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
getNumericValue(c));
                     } else if (criterion instanceof Query.Between c) {
                         if (c.getFrom() instanceof String && c.getTo() 
instanceof String) {
                             return 
cb.between(fromsByProvider.getFullyQualifiedPath(c.getProperty()), (String) 
c.getFrom(), (String) c.getTo());
@@ -154,13 +152,13 @@ public class PredicateGenerator {
                     } else if (criterion instanceof Query.SizeEquals c) {
                         return 
cb.equal(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
c.getValue());
                     } else if (criterion instanceof Query.SizeGreaterThan c) {
-                        return 
cb.gt(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), (Number) 
c.getValue());
+                        return 
cb.gt(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
getNumericValue(c));
                     } else if (criterion instanceof 
Query.SizeGreaterThanEquals c) {
-                        return 
cb.ge(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), (Number) 
c.getValue());
+                        return 
cb.ge(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
getNumericValue(c));
                     } else if (criterion instanceof Query.SizeLessThan c) {
-                        return 
cb.lt(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), (Number) 
c.getValue());
+                        return 
cb.lt(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
getNumericValue(c));
                     } else if (criterion instanceof Query.SizeLessThanEquals 
c) {
-                        return 
cb.le(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), (Number) 
c.getValue());
+                        return 
cb.le(cb.size(fromsByProvider.getFullyQualifiedPath(c.getProperty())), 
getNumericValue(c));
                     } else if(criterion instanceof Query.In || criterion 
instanceof Query.NotIn) {
                         var queryableCriteria = 
getQueryableCriteriaFromInCriteria(criterion);
                         if (Objects.nonNull(queryableCriteria)) {
@@ -332,4 +330,16 @@ public class PredicateGenerator {
                 .map(expressible ->  
expressible.getExpressibleJavaType().getJavaTypeClass())
                 .orElse(null);
     }
+
+    private static Number getNumericValue(Query.PropertyCriterion criterion) {
+        Object value = criterion.getValue();
+        if (value instanceof Number) {
+            return (Number) value;
+        }
+        String operationName = criterion.getClass().getSimpleName();
+        throw new ConfigurationException(
+                String.format("Operation '%s' on property '%s' only accepts a 
numeric value, but received a %s",
+                        operationName, criterion.getProperty(), (value == null 
? "null" : value.getClass().getName())));
+    }
+
 }
diff --git 
a/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcherSpec.groovy
 
b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcherSpec.groovy
index 8d141d1b0b..11f865c48a 100644
--- 
a/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcherSpec.groovy
+++ 
b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/CascadeBehaviorFetcherSpec.groovy
@@ -42,6 +42,7 @@ class CascadeBehaviorFetcherSpec extends 
HibernateGormDatastoreSpec {
             ["many-to-many (inverse side)"  , Tag_BT            , "posts"   , 
Post            , NONE.getValue()],
             ["many-to-many (circular superclass)", Mammal, "dogs", Dog, 
NONE.getValue()],
             ["many-to-one (belongsTo)"      , Book_BT_Default   , "author"  , 
AW_Default_Bi   , NONE.getValue()],
+            ["many-to-one (unidirectional)" , A                 , "manyToOne", 
ManyToOne      , SAVE_UPDATE.getValue()],
             ["many-to-one (bidirectional but superclass)"      , Bird   , 
"canary"  , Canary   , NONE.getValue()],
 
 //             --- Additional Hibernate 6+ specific scenarios ---
@@ -141,6 +142,13 @@ class Buffalo{}
 @Entity class Book_BT_Default { String title; static belongsTo = [author: 
AW_Default_Bi] }
 @Entity class AW_Default_Bi { static hasMany = [books: Book_BT_Default] }
 
+@Entity
+class A {
+    ManyToOne manyToOne
+}
+@Entity
+class ManyToOne {
+}
 
 
 
diff --git 
a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/domains/Child_BT_Default_P.groovy
 
b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/domains/Child_BT_Default_P.groovy
new file mode 100644
index 0000000000..c36559db11
--- /dev/null
+++ 
b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/domains/Child_BT_Default_P.groovy
@@ -0,0 +1,9 @@
+package org.apache.grails.data.testing.tck.domains
+
+import grails.gorm.annotation.Entity
+
+@Entity
+class Child_BT_Default_P {
+    String title
+    static belongsTo = [owner: Owner_Default_Bi_P]
+}
diff --git 
a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/domains/Owner_Default_Bi_P.groovy
 
b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/domains/Owner_Default_Bi_P.groovy
new file mode 100644
index 0000000000..962d7d788c
--- /dev/null
+++ 
b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/domains/Owner_Default_Bi_P.groovy
@@ -0,0 +1,10 @@
+package org.apache.grails.data.testing.tck.domains
+
+import grails.gorm.annotation.Entity
+
+@Entity
+class Owner_Default_Bi_P {
+    String name
+    Set<Child_BT_Default_P> children
+    static hasMany = [children: Child_BT_Default_P]
+}
diff --git 
a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/SizeQuerySpec.groovy
 
b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/SizeQuerySpec.groovy
index 7e724ca50a..0524e5b6cd 100644
--- 
a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/SizeQuerySpec.groovy
+++ 
b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/SizeQuerySpec.groovy
@@ -18,317 +18,172 @@
  */
 package org.apache.grails.data.testing.tck.tests
 
-import org.apache.grails.data.testing.tck.domains.SimpleCountry
-import org.apache.grails.data.testing.tck.domains.Person
 import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec
+import org.apache.grails.data.testing.tck.domains.Child_BT_Default_P
+import org.apache.grails.data.testing.tck.domains.Owner_Default_Bi_P
+import spock.lang.Unroll
 
 /**
  * Tests for querying the size of collections etc.
  */
 class SizeQuerySpec extends GrailsDataTckSpec {
     void setupSpec() {
-        manager.addAllDomainClasses([SimpleCountry, Person])
+        manager.addAllDomainClasses([Owner_Default_Bi_P, Child_BT_Default_P])
     }
 
-
-    void "Test sizeLe criterion"() {
-        given: "A country with only 1 resident"
-        Person p = new Person(firstName: "Fred", lastName: "Flinstone")
-        SimpleCountry c = new SimpleCountry(name: "Dinoville")
-                .addToResidents(p)
+    private void setupTestData() {
+        // Owner A has 1 child
+        new Owner_Default_Bi_P(name: "Owner A")
+                .addToChildren(new Child_BT_Default_P(title: "Child 1"))
                 .save(flush: true)
 
-        new SimpleCountry(name: "Springfield")
-                .addToResidents(firstName: "Homer", lastName: "Simpson")
-                .addToResidents(firstName: "Bart", lastName: "Simpson")
-                .addToResidents(firstName: "Marge", lastName: "Simpson")
+        // Owner B has 2 children
+        new Owner_Default_Bi_P(name: "Owner B")
+                .addToChildren(new Child_BT_Default_P(title: "Child 5"))
+                .addToChildren(new Child_BT_Default_P(title: "Child 6"))
                 .save(flush: true)
 
-        new SimpleCountry(name: "Miami")
-                .addToResidents(firstName: "Dexter", lastName: "Morgan")
-                .addToResidents(firstName: "Debra", lastName: "Morgan")
+        // Owner C has 3 children
+        new Owner_Default_Bi_P(name: "Owner C")
+                .addToChildren(new Child_BT_Default_P(title: "Child 2"))
+                .addToChildren(new Child_BT_Default_P(title: "Child 3"))
+                .addToChildren(new Child_BT_Default_P(title: "Child 4"))
                 .save(flush: true)
 
         manager.session.clear()
+    }
 
-        when: "We query for countries with 1 resident"
-        def results = SimpleCountry.withCriteria {
-            sizeLe "residents", 3
-            order "name"
-        }
-
-        then: "We get the correct result back"
-        results != null
-        results.size() == 3
-        results[0].name == 'Dinoville'
-        results[1].name == 'Miami'
-        results[2].name == 'Springfield'
+    @Unroll("Test sizeLe criterion with size #size expects #expectedNames")
+    void "Test sizeLe criterion"(int size, List<String> expectedNames) {
+        given: "A set of owners with 1, 2, and 3 children"
+        setupTestData()
 
-        when: "We query for countries with 2 resident"
-        results = SimpleCountry.withCriteria {
-            sizeLe "residents", 2
+        when: "We query for owners with at most #size children"
+        def results = Owner_Default_Bi_P.withCriteria {
+            sizeLe "children", size
             order "name"
         }
 
-        then: "We get the correct result back"
-        results != null
-        results.size() == 2
-        results[0].name == 'Dinoville'
-        results[1].name == 'Miami'
+        then: "We get the correct owners back"
+        results*.name == expectedNames
 
-        when: "We query for countries with 2 residents"
-        results = SimpleCountry.withCriteria {
-            sizeLe "residents", 1
-        }
-
-        then: "we get 1 result back"
-        results.size() == 1
+        where:
+        size | expectedNames
+        3    | ['Owner A', 'Owner B', 'Owner C']
+        2    | ['Owner A', 'Owner B']
+        1    | ['Owner A']
+        0    | []
     }
 
-    void "Test sizeLt criterion"() {
-        given: "A country with only 1 resident"
-        Person p = new Person(firstName: "Fred", lastName: "Flinstone")
-        SimpleCountry c = new SimpleCountry(name: "Dinoville")
-                .addToResidents(p)
-                .save(flush: true)
+    @Unroll("Test sizeLt criterion with size #size expects #expectedNames")
+    void "Test sizeLt criterion"(int size, List<String> expectedNames) {
+        given: "A set of owners with 1, 2, and 3 children"
+        setupTestData()
 
-        new SimpleCountry(name: "Springfield")
-                .addToResidents(firstName: "Homer", lastName: "Simpson")
-                .addToResidents(firstName: "Bart", lastName: "Simpson")
-                .addToResidents(firstName: "Marge", lastName: "Simpson")
-                .save(flush: true)
-
-        new SimpleCountry(name: "Miami")
-                .addToResidents(firstName: "Dexter", lastName: "Morgan")
-                .addToResidents(firstName: "Debra", lastName: "Morgan")
-                .save(flush: true)
-
-        manager.session.clear()
-
-        when: "We query for countries with 1 resident"
-        def results = SimpleCountry.withCriteria {
-            sizeLt "residents", 3
+        when: "We query for owners with less than #size children"
+        def results = Owner_Default_Bi_P.withCriteria {
+            sizeLt "children", size
             order "name"
         }
 
-        then: "We get the correct result back"
-        results != null
-        results.size() == 2
-        results[0].name == 'Dinoville'
-        results[1].name == 'Miami'
-
-        when: "We query for countries with 2 resident"
-        results = SimpleCountry.withCriteria {
-            sizeLt "residents", 2
-        }
-
-        then: "We get the correct result back"
-        results != null
-        results.size() == 1
-        results[0].name == 'Dinoville'
+        then: "We get the correct owners back"
+        results*.name == expectedNames
 
-        when: "We query for countries with 2 residents"
-        results = SimpleCountry.withCriteria {
-            sizeLt "residents", 1
-        }
-
-        then: "we get no results back"
-        results.size() == 0
+        where:
+        size | expectedNames
+        3    | ['Owner A', 'Owner B']
+        2    | ['Owner A']
+        1    | []
     }
 
-    void "Test sizeGt criterion"() {
-        given: "A country with only 1 resident"
-        Person p = new Person(firstName: "Fred", lastName: "Flinstone")
-        SimpleCountry c = new SimpleCountry(name: "Dinoville")
-                .addToResidents(p)
-                .save(flush: true)
-
-        new SimpleCountry(name: "Springfield")
-                .addToResidents(firstName: "Homer", lastName: "Simpson")
-                .addToResidents(firstName: "Bart", lastName: "Simpson")
-                .addToResidents(firstName: "Marge", lastName: "Simpson")
-                .save(flush: true)
-
-        new SimpleCountry(name: "Miami")
-                .addToResidents(firstName: "Dexter", lastName: "Morgan")
-                .addToResidents(firstName: "Debra", lastName: "Morgan")
-                .save(flush: true)
-
-        manager.session.clear()
+    @Unroll("Test sizeGt criterion with size #size expects #expectedNames")
+    void "Test sizeGt criterion"(int size, List<String> expectedNames) {
+        given: "A set of owners with 1, 2, and 3 children"
+        setupTestData()
 
-        when: "We query for countries with 1 resident"
-        def results = SimpleCountry.withCriteria {
-            sizeGt "residents", 1
+        when: "We query for owners with more than #size children"
+        def results = Owner_Default_Bi_P.withCriteria {
+            sizeGt "children", size
             order "name"
         }
 
-        then: "We get the correct result back"
-        results != null
-        results.size() == 2
-        results[0].name == 'Miami'
-        results[1].name == 'Springfield'
+        then: "We get the correct owners back"
+        results*.name == expectedNames
 
-        when: "We query for countries with 2 resident"
-        results = SimpleCountry.withCriteria {
-            sizeGt "residents", 2
-        }
-
-        then: "We get the correct result back"
-        results != null
-        results.size() == 1
-        results[0].name == 'Springfield'
-
-        when: "We query for countries with 2 residents"
-        results = SimpleCountry.withCriteria {
-            sizeGt "residents", 5
-        }
-
-        then: "we get no results back"
-        results.size() == 0
+        where:
+        size | expectedNames
+        0    | ['Owner A', 'Owner B', 'Owner C']
+        1    | ['Owner B', 'Owner C']
+        2    | ['Owner C']
+        3    | []
     }
 
-    void "Test sizeGe criterion"() {
-        given: "A country with only 1 resident"
-        Person p = new Person(firstName: "Fred", lastName: "Flinstone")
-        SimpleCountry c = new SimpleCountry(name: "Dinoville")
-                .addToResidents(p)
-                .save(flush: true)
-
-        new SimpleCountry(name: "Springfield")
-                .addToResidents(firstName: "Homer", lastName: "Simpson")
-                .addToResidents(firstName: "Bart", lastName: "Simpson")
-                .addToResidents(firstName: "Marge", lastName: "Simpson")
-                .save(flush: true)
+    @Unroll("Test sizeGe criterion with size #size expects #expectedNames")
+    void "Test sizeGe criterion"(int size, List<String> expectedNames) {
+        given: "A set of owners with 1, 2, and 3 children"
+        setupTestData()
 
-        new SimpleCountry(name: "Miami")
-                .addToResidents(firstName: "Dexter", lastName: "Morgan")
-                .addToResidents(firstName: "Debra", lastName: "Morgan")
-                .save(flush: true)
-
-        manager.session.clear()
-
-        when: "We query for countries with 1 resident"
-        def results = SimpleCountry.withCriteria {
-            sizeGe "residents", 1
+        when: "We query for owners with at least #size children"
+        def results = Owner_Default_Bi_P.withCriteria {
+            sizeGe "children", size
             order "name"
         }
 
-        then: "We get the correct result back"
-        results != null
-        results.size() == 3
-        results[0].name == 'Dinoville'
-        results[1].name == 'Miami'
-        results[2].name == 'Springfield'
+        then: "We get the correct owners back"
+        results*.name == expectedNames
 
-        when: "We query for countries with 2 resident"
-        results = SimpleCountry.withCriteria {
-            sizeGe "residents", 2
-            order "name"
-        }
-
-        then: "We get the correct result back"
-        results != null
-        results.size() == 2
-        results[0].name == 'Miami'
-        results[1].name == 'Springfield'
-
-        when: "We query for countries with 2 residents"
-        results = SimpleCountry.withCriteria {
-            sizeGe "residents", 5
-        }
-
-        then: "we get no results back"
-        results.size() == 0
+        where:
+        size | expectedNames
+        1    | ['Owner A', 'Owner B', 'Owner C']
+        2    | ['Owner B', 'Owner C']
+        3    | ['Owner C']
+        4    | []
     }
 
-    void "Test sizeEq criterion"() {
-        given: "A country with only 1 resident"
-        Person p = new Person(firstName: "Fred", lastName: "Flinstone")
-        SimpleCountry c = new SimpleCountry(name: "Dinoville")
-                .addToResidents(p)
-                .save(flush: true)
-
-        new SimpleCountry(name: "Springfield")
-                .addToResidents(firstName: "Homer", lastName: "Simpson")
-                .addToResidents(firstName: "Bart", lastName: "Simpson")
-                .addToResidents(firstName: "Marge", lastName: "Simpson")
-                .save(flush: true)
-
-        manager.session.clear()
-
-        when: "We query for countries with 1 resident"
-        def results = SimpleCountry.withCriteria {
-            sizeEq "residents", 1
-        }
+    @Unroll("Test sizeEq criterion with size #size expects #expectedNames")
+    void "Test sizeEq criterion"(int size, List<String> expectedNames) {
+        given: "A set of owners with 1, 2, and 3 children"
+        setupTestData()
 
-        then: "We get the correct result back"
-        results != null
-        results.size() == 1
-        results[0].name == 'Dinoville'
-
-        when: "We query for countries with 3 resident"
-        results = SimpleCountry.withCriteria {
-            sizeEq "residents", 3
+        when: "We query for owners with exactly #size children"
+        def results = Owner_Default_Bi_P.withCriteria {
+            sizeEq "children", size
+            order "name"
         }
 
-        then: "We get the correct result back"
-        results != null
-        results.size() == 1
-        results[0].name == 'Springfield'
-
-        when: "We query for countries with 2 residents"
-        results = SimpleCountry.withCriteria {
-            sizeEq "residents", 2
-        }
+        then: "We get the correct owners back"
+        results*.name == expectedNames
 
-        then: "we get no results back"
-        results.size() == 0
+        where:
+        size | expectedNames
+        1    | ['Owner A']
+        2    | ['Owner B']
+        3    | ['Owner C']
+        4    | []
     }
 
-    void "Test sizeNe criterion"() {
-        given: "A country with only 1 resident"
-        Person p = new Person(firstName: "Fred", lastName: "Flinstone")
-        SimpleCountry c = new SimpleCountry(name: "Dinoville")
-                .addToResidents(p)
-                .save(flush: true)
-
-        new SimpleCountry(name: "Springfield")
-                .addToResidents(firstName: "Homer", lastName: "Simpson")
-                .addToResidents(firstName: "Bart", lastName: "Simpson")
-                .addToResidents(firstName: "Marge", lastName: "Simpson")
-                .save(flush: true)
-
-        manager.session.clear()
-
-        when: "We query for countries that don't have 1 resident"
-        def results = SimpleCountry.withCriteria {
-            sizeNe "residents", 1
-        }
-
-        then: "We get the correct result back"
-        results != null
-        results.size() == 1
-        results[0].name == 'Springfield'
-
-        when: "We query for countries who don't have 3 resident"
-        results = SimpleCountry.withCriteria {
-            sizeNe "residents", 3
+    @Unroll("Test sizeNe criterion for #description expects #expectedNames")
+    void "Test sizeNe criterion"(String description, Closure queryLogic, 
List<String> expectedNames) {
+        given: "A set of owners with 1, 2, and 3 children"
+        setupTestData()
+
+        when: "We query for owners where the number of children meets a 
condition"
+        
+        def results = Owner_Default_Bi_P.withCriteria {
+            // Set the delegate of the query closure to the criteria builder 
and call it
+            queryLogic.delegate = delegate
+            queryLogic.call()
+            order "name"
         }
 
-        then: "We get the correct result back"
-        results != null
-        results.size() == 1
-        results[0].name == 'Dinoville'
-
-        when: "We query for countries with 2 residents"
-        results = SimpleCountry.withCriteria {
-            and {
-                sizeNe "residents", 1
-                sizeNe "residents", 3
-            }
-        }
+        then: "We get the correct owners back"
+        results*.name == expectedNames
 
-        then: "we get no results back"
-        results.size() == 0
+        where:
+        description           | queryLogic                                     
      | expectedNames
+        "size != 1"           | { sizeNe "children", 1 }                       
      | ['Owner B', 'Owner C']
+        "size != 2"           | { sizeNe "children", 2 }                       
      | ['Owner A', 'Owner C']
+        "size != 3"           | { sizeNe "children", 3 }                       
      | ['Owner A', 'Owner B']
+        "size != 1 and != 3"  | { and { sizeNe "children", 1; sizeNe 
"children", 3 } } | ['Owner B']
     }
-}
+}
\ No newline at end of file

Reply via email to