jamesfredley commented on code in PR #16875:
URL: https://github.com/apache/grails-core/pull/16875#discussion_r4495290665


##########
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/query/Query.java:
##########
@@ -691,6 +691,122 @@ protected FetchType fetchStrategy(String property) {
         return FetchType.LAZY;
     }
 
+    /**
+     * Runs the subqueries inside the given criteria and returns a copy of the 
criteria that holds their
+     * results, for datastores that cannot evaluate a subquery themselves 
(MongoDB, Neo4j). The criteria
+     * passed in are never changed, so a query that is executed twice runs its 
subqueries twice.
+     *
+     * <p>A comparison ({@code ==}, {@code !=}, {@code >}, {@code >=}, {@code 
<}, {@code <=}) against a
+     * subquery, such as {@code age > avg(age)}, takes the one value the 
subquery returns, and {@code in}
+     * and {@code notIn} take all of them. A comparison against a subquery 
that returns no row matches no row,
+     * as in SQL, where the comparison is unknown. A subquery that returns 
more than one row for a comparison is
+     * rejected. Subqueries inside an association block are resolved too. 
{@code exists}, {@code notExists} and the {@code all} and {@code some} forms 
are rejected
+     * with an {@link InvalidDataAccessResourceUsageException}, as they are 
not supported.</p>
+     *
+     * @param criteria the criteria of this query
+     * @return the same instance when it holds no subquery, otherwise a copy 
with the subqueries resolved
+     * @throws InvalidDataAccessResourceUsageException for a form that cannot 
be resolved
+     * @since 8.0.1
+     */
+    protected Junction resolveSubqueries(Junction criteria) {
+        return (Junction) resolveSubqueries((Criterion) criteria);
+    }
+
+    private Criterion resolveSubqueries(Criterion criterion) {
+        if (criterion instanceof Junction junction) {
+            List<Criterion> resolved = new 
ArrayList<>(junction.getCriteria().size());
+            boolean changed = false;
+            for (Criterion nested : junction.getCriteria()) {
+                Criterion replacement = resolveSubqueries(nested);
+                changed |= replacement != nested;
+                resolved.add(replacement);
+            }
+            if (!changed) {
+                return junction;
+            }
+            if (junction instanceof Disjunction) {
+                return new Disjunction(resolved);
+            }
+            if (junction instanceof Negation) {
+                Negation negation = new Negation();
+                resolved.forEach(negation::add);
+                return negation;
+            }
+            return new Conjunction(resolved);
+        }
+        if (criterion instanceof Exists || criterion instanceof NotExists) {
+            throw unsupportedSubquery(criterion.getClass().getSimpleName());
+        }
+        if (criterion instanceof In in && in.getSubquery() != null) {
+            return new In(in.getProperty(), subqueryValues(in.getSubquery()));
+        }
+        if (criterion instanceof NotIn notIn) {
+            Negation negation = new Negation();
+            negation.add(new In(notIn.getProperty(), 
subqueryValues(notIn.getSubquery())));
+            return negation;
+        }
+        if (criterion instanceof SubqueryCriterion) {
+            throw unsupportedSubquery(criterion.getClass().getSimpleName());
+        }
+        if (criterion instanceof AssociationQuery associationQuery) {
+            Junction nested = associationQuery.getCriteria();
+            Junction resolved = (Junction) resolveSubqueries((Criterion) 
nested);
+            if (resolved == nested) {
+                return criterion;
+            }
+            AssociationQuery copy = new 
AssociationQuery(associationQuery.getSession(), associationQuery.getEntity(),
+                    associationQuery.getAssociation());
+            copy.criteria = resolved;
+            return copy;
+        }
+        if (criterion instanceof PropertyCriterion property && 
property.getValue() instanceof QueryableCriteria subquery) {
+            if (!(criterion instanceof Equals || criterion instanceof 
NotEquals || criterion instanceof GreaterThan ||
+                    criterion instanceof GreaterThanEquals || criterion 
instanceof LessThan ||
+                    criterion instanceof LessThanEquals)) {
+                throw 
unsupportedSubquery(criterion.getClass().getSimpleName());
+            }
+            List<?> rows = subquery.list();
+            if (rows == null || rows.isEmpty()) {

Review Comment:
   not { gt('age', Person.where { firstName == 'Absent' }.property('age')) } 
replaces the empty subquery with age IN []. The negation is true, so every 
person is returned. An empty scalar comparison should match no rows, including 
under not.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to