Fix embedded where queries#15962
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## 8.0.x #15962 +/- ##
==================================================
+ Coverage 49.6709% 51.0308% +1.3599%
- Complexity 17017 17414 +397
==================================================
Files 2004 2012 +8
Lines 93896 94202 +306
Branches 16448 16460 +12
==================================================
+ Hits 46639 48072 +1433
+ Misses 40078 38926 -1152
- Partials 7179 7204 +25
🚀 New features to boost your workflow:
|
borinquenkid
left a comment
There was a problem hiding this comment.
Needs coverage added as per codecov
|
I've gone ahead and moved the duplicated tests in h5/h7 for where queries to the tck as well as part of this |
borinquenkid
left a comment
There was a problem hiding this comment.
Got it. Looking at the expanded file list in the Codecov report, the remaining coverage gaps are spread across the Simple, Core, and MongoDB modules.
Here is the regenerated list of missing test scenarios needed to cover the newly visible files:
1. SimpleMapQuery.groovy (grails-data-simple)
The newly added Embedded handling block (lines 295-300) inside queryAssociation is completely uncovered (0% patch coverage).
- Missing Scenario: You need a test that executes a criteria or Where query against an
Embeddedproperty, specifically utilizing theSimpleMapEntityPersister(the in-memory/simple datastore). - Note: To fully cover this, ensure the test hits both branches of the
if (embedded != null)check on line 297 (i.e., one record where the embedded component exists, and one where it is null).
2. DetachedCriteriaTransformer.java (grails-datamapping-core)
The AST transformation logic added to retain the type of embedded components (lines 812-818) is completely missed.
- Missing Scenario: A unit test verifying the AST transformation of a Where query block that targets an embedded component. The test needs to trigger compilation of a detached criteria where
AstUtils.isDomainClass(type)is false, forcing it into thegetAssociationTypeFromGenericsblock to retain theassociationType.
3. MongoQuery.java (grails-data-mongodb)
Most of the MongoDB embedded query rewriting is covered, but there are a few missed branches (partial coverage) regarding logical operators within embedded queries.
- Missing Scenario A (Line 202): The condition
if (associatedEntity instanceof EmbeddedPersistentEntity || association instanceof Embedded)has partial coverage. You likely have a test covering one side of this OR condition but not the other. - Missing Scenario B (Line 761): The condition
if (key.charAt(0) == '$' && value instanceof List)insideprefixEmbeddedQueryhas partial coverage. You need a MongoDB query on an embedded property that utilizes a logical operator like$andor$or(which generates a list of conditions) to hit thetruebranch of this check. - Missing Scenario C (Line 764): The condition
if (element instanceof Document)within the list iteration also has partial coverage. The test for Scenario B will likely cover thetruebranch, but you may need a scenario where an element inside a Mongo$inor similar array operator evaluates tofalse(e.g., checking a list of primitives instead of nested Documents).
codeconsole
left a comment
There was a problem hiding this comment.
Approving with a few minor changes requested (inline). The fix is correct and consistent across all four implementations, and I verified the two extra concerns beyond the diff itself:
MongoDB impact: positive, not neutral. The MongoQuery change fixes a real silent wrong-results bug — the old code prefixed every key of an embedded sub-query, so an embedded block containing a disjunction produced {"extRef1.$or": [...]}, a key that matches nothing. The new recursive rewrite keeps logical operators at the correct level, and the new TCK tests exercise exactly this shape on the Mongo suites (all green on the head commit).
Performance impact: neutral to slightly positive. Non-embedded association paths gain only an instanceof/attribute-type branch. The AbstractDetachedCriteria null-check fix is an actual win — the old Groovy-truth check invoked DetachedCriteria.asBoolean(), executing the criteria as a spurious database query on every repeated association-block reference. The Mongo handler also now computes getPropertyName() once instead of per key.
Two things to clean up in the PR description before merge:
- It states "Hibernate 7 and MongoDB required no code changes" — no longer true; both
CriteriaMethodInvokerandMongoQueryhave functional changes in the current head. - The closing sentence ("Say the word and I'll run the full build in the background...") is a leftover conversational artifact and should be removed — the description becomes the merge record.
- Rename the duplicate WhereQueryEmbeddedSpec feature method so both junction tests report under distinct display names - Note in CriteriaMethodInvoker that an explicit join-type argument on an embedded block is intentionally ignored (a component cannot be joined; its columns live in the owning entity's table) - MongoQuery: throw UnsupportedOperationException when an embedded sub-query contains a non-rewritable $-operator (e.g. $where from a property-to-property comparison, or $text) instead of mis-prefixing it into a key that silently matches nothing; use List<Object> in the rewrite to avoid unchecked warnings - Cover the new fail-loud behavior in EmbeddedAssociationSpec via the public criteria API
✅ All tests passed ✅🏷️ Commit: eaf194d Learn more about TestLens at testlens.app. |
Description
Fixes #15955
where {}queries failed with a HibernateQueryException("Criteria objects cannot be created directly on components") when a predicate referenced a property of an embedded component (e.g.extRef1.value =~ "%ABC%") and was combined with any other condition (&&,||, or additional criteria). A lone embedded predicate worked becauseAbstractHibernateQuery.add()special-casesEmbeddedassociations, but once the predicate was nested inside a junction, translation went throughAbstractHibernateCriterionAdapter, whoseDetachedAssociationCriteriaandAssociationQueryadapters unconditionally created a Hibernate sub-criteria/alias on the component — which Hibernate rejects.Changes:
DetachedCriteriaTransformerno longer discards an embedded block's component type when resolving the association type from generics (a collection association carries its element type in its generics; an embedded component does not), which previously caused the block's criteria to be silently dropped. It also recomputes variable scopes after applying the where transform so the generated nested closures capture exactly the variables they reference instead of inheriting the outer closure's scope nondeterministically.AbstractDetachedCriterianow uses explicit null checks instead of Groovy truth when looking up an existing association criteria — Groovy truth on aDetachedCriteriainvokesasBoolean(), which executed the criteria as a spurious query on every repeated association-block reference (and failed outright for embedded components, whose class is not a queryable root entity).DetachedAssociationCriteriaandAssociationQuerycriterion adapters now short-circuit forEmbeddedassociations and apply the nested criteria using dotted property paths (extRef1.value), mirroring the existing top-level handling inAbstractHibernateQuery.add().CriteriaMethodInvokernow recognizes JPAEMBEDDEDattributes (which the JPA metamodel does not consider associations, but the GORM model does) and builds aDetachedAssociationCriteriafor the embedded block without creating a join — a component's columns live in the owning entity's table, so an explicit join-type argument on an embedded block is intentionally ignored.MongoQuerypreviously prefixed every key of an embedded sub-query with the embedded property's path, so an embedded block containing a junction produced keys like{"extRef1.$or": [...]}that silently matched nothing. The rewrite is now recursive: logical operators ($and,$or, …) stay at the correct level and only property keys are qualified. A$-operator whose value cannot be rewritten (e.g.$wherefrom a property-to-property comparison, or$text) inside an embedded block now throwsUnsupportedOperationExceptioninstead of silently matching nothing.SimpleMapQuery.queryAssociation()treatedEmbedded(aToOnesubtype) as a real association and calledsession.retrieve()with the inlined embedded map as an id, throwingNonPersistentTypeException. It now evaluates the criteria directly against the stored embedded value.WhereQueryEmbeddedSpecplusWorkItem/SystemManaged/ExternalReftest domains reproducing the reported shape (embedded component declared in an abstract non-entity superclass). Covers=~and==on embedded properties, chainedquery.where {}composition, aliased blocks, and the conjunction/disjunction cases that triggered the bug. Where-query specs that were previously duplicated per implementation (WhereQueryIssueVerificationSpec,WhereQueryLeftJoinSpec,DeleteAllSpec) were moved into the TCK, and the TCK test properties were standardized to one location in the Gradle test configs. The TCK runs against all four implementations: hibernate5, hibernate7, simple (in-memory), and mongodb.PerTestRecordingSpec: recording-directory discovery is now scoped to directories timestamped at or after the current JVM's start time instead of racing on the "most recent" directory, which broke under parallel test execution.All test suites for the affected modules pass, along with the project style checks. The full
./gradlew build --rerun-taskssuite was not run; verification covered the affected modules.Contributor Checklist
Please review the following checklist before submitting your pull request. Pull requests that do not meet these requirements may be closed without review.
Issue and Scope
PerTestRecordingSpecflaky-test fix.)7.0.x): Bug fixes only. No new features or API changes.7.1.x): New features are welcome, but breaking existing APIs must be avoided.8.0.x): Reserved for major changes. Breaking API changes are permitted.Code Quality
./gradlew build --rerun-tasks../gradlew codeStyleand resolved any violations. See Code Style for details.Licensing and Attribution
Documentation