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]