Skip to content

fix: where query variable scoping fails inside @Transactional methods#15448

Merged
jdaugherty merged 2 commits intoapache:7.0.xfrom
jamesfredley:fix/where-query-transactional-variable-scope
Feb 25, 2026
Merged

fix: where query variable scoping fails inside @Transactional methods#15448
jdaugherty merged 2 commits intoapache:7.0.xfrom
jamesfredley:fix/where-query-transactional-variable-scope

Conversation

@jamesfredley
Copy link
Contributor

Summary

Fixes #11464 - GORM where queries with local variable names matching domain property names (e.g. Book.where { title == title }) fail with MissingMethodException when called from @Transactional methods, but work correctly outside of them.

See also: GROOVY-9629

Root Cause

Three-step AST transform interaction:

  1. DetachedCriteriaTransformer (WHERE_ORDER priority) transforms where { title == title } into criteria API calls like delegate.title(closure) with implicitThis = true (default in MethodCallExpression constructor).

  2. TransactionalTransform runs later, moves the method body to $tt__methodName(), then re-runs Groovy's VariableScopeVisitor on the renamed method (AbstractMethodDecoratingTransformation.groovy L356-362).

  3. VariableScopeVisitor sees delegate.title(closure) with implicitThis=true, finds a local variable named title in scope, and rewrites it to title.call(closure) - completely breaking the transformed code.

Fix

Set implicitThis = false on all MethodCallExpression nodes generated by DetachedCriteriaTransformer where the object expression is delegate. This is semantically correct - the call IS explicitly on delegate, not implicit this. It prevents VariableScopeVisitor from rewriting the call.

4 locations modified in DetachedCriteriaTransformer.java:

  • Association query on delegate (line ~790)
  • Nested association chain traversal on delegate (line ~1064)
  • Embedded property query on delegate (line ~1127)
  • Domain association property query on delegate (line ~1161)

This pattern is already used in AbstractMethodDecoratingTransformation.groovy (line 370) for similar reasons.

Test Coverage

  • Unit tests: 2 new tests in WhereMethodSpec verifying that @Transactional + @ApplyDetachedCriteriaTransform classes with variable name collisions compile and instantiate successfully
  • Functional tests: 5 new integration tests in TransactionalWhereQueryVariableScopeSpec verifying runtime behavior of where queries with variable name collisions in @Transactional service methods
  • All 82 existing WhereMethodSpec tests continue to pass

Reproducer

A standalone reproducer application is available at: https://github.com/jamesfredley/grails-where-query-transactional-scope-bug

…chedCriteriaTransformer

When @transactional methods contain where queries with local variable
names matching domain property names (e.g. Book.where { title == title }),
TransactionalTransform re-runs VariableScopeVisitor on the renamed method
body. VariableScopeVisitor sees delegate.property(closure) calls with
implicitThis=true, finds a local variable with the same name in scope,
and rewrites them to variable.call(closure) - causing MissingMethodException
at runtime.

The fix sets implicitThis=false on all MethodCallExpression nodes generated
by DetachedCriteriaTransformer where the object expression is 'delegate'.
This is semantically correct since the call IS explicitly on delegate, not
implicit this, and prevents VariableScopeVisitor from rewriting the call.

Fixes apache#11464

Assisted-by: OpenCode <opencode@opencode.ai>
@jamesfredley jamesfredley self-assigned this Feb 24, 2026
@jamesfredley jamesfredley moved this to In Progress in Apache Grails Feb 24, 2026
@jamesfredley jamesfredley added this to the grails:7.0.8 milestone Feb 24, 2026
@jamesfredley jamesfredley marked this pull request as ready for review February 24, 2026 18:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a GORM where-query failure inside @Transactional methods when a local variable name collides with a domain property name, by preventing Groovy’s VariableScopeVisitor from rewriting transformed delegate.*(...) calls.

Changes:

  • Update DetachedCriteriaTransformer to set implicitThis=false on generated MethodCallExpressions targeting delegate.
  • Add functional integration coverage in the gorm test example app for transactional where-query variable collisions.
  • Add unit-level compilation coverage in WhereMethodSpec for transactional + detached-criteria transforms with name collisions.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/query/transform/DetachedCriteriaTransformer.java Marks generated delegate method calls as explicit (implicitThis=false) to avoid scope-rewrite breakage.
grails-datamapping-core-test/src/test/groovy/grails/gorm/tests/WhereMethodSpec.groovy Adds compilation-oriented tests for transactional contexts with variable/property name collisions.
grails-test-examples/gorm/grails-app/services/gorm/WhereQueryVariableScopeService.groovy Introduces a service containing transactional where queries that intentionally collide with local variable names.
grails-test-examples/gorm/src/integration-test/groovy/gorm/TransactionalWhereQueryVariableScopeSpec.groovy Adds integration tests exercising runtime behavior for the reported transactional scoping regression.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

The comment incorrectly described the method as a 'Non-transactional
baseline' when it is actually annotated with @transactional(readOnly = true).
Updated to accurately describe it as a read-only transactional variant.

Assisted-by: OpenCode <opencode@opencode.ai>
Copy link
Contributor

@davydotcom davydotcom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We always suspected this had to do with some type of transfomer clobbering over another but was confusing as to what exactly was tripping it up. ordering didnt seem to do much and this explains that. Great find this is a big step in making where{} criteria more consistent in its behavior

@jdaugherty jdaugherty merged commit 88252a0 into apache:7.0.x Feb 25, 2026
32 checks passed
@github-project-automation github-project-automation bot moved this from In Progress to Done in Apache Grails Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Method with @Transactional and where query with local variable causes exception

4 participants