Hi James,
I'm trying the alias because the code below was not working:
if (filterParams.containsKey('workPackage.code')) query = query.where
{ workPackage.code == filterParams.'workPackage.code' }
This neither:
if (filterParams.containsKey('workPackage.code')) query = query.where {
workPackage {
code == filterParams.'workPackage.code'
}
}
This is the exception:
Caused by: org.hibernate.QueryException: could not resolve property:
workPackage.code of: momentum.work.TWorkAssignment
at
org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:78)
at
org.hibernate.persister.entity.AbstractPropertyMapping.toColumns(AbstractPropertyMapping.java:93)
at
org.hibernate.persister.entity.BasicEntityPropertyMapping.toColumns(BasicEntityPropertyMapping.java:43)
at
org.hibernate.persister.entity.AbstractEntityPersister.toColumns(AbstractEntityPersister.java:2023)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getColumns(CriteriaQueryTranslator.java:566)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getColumnsUsingProjection(CriteriaQueryTranslator.java:495)
at org.hibernate.criterion.Order.toSqlString(Order.java:109)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getOrderBy(CriteriaQueryTranslator.java:440)
at
org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:106)
at
org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:75)
at
org.hibernate.loader.criteria.CriteriaLoader.<init>(CriteriaLoader.java:80)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1908)
at org.hibernate.internal.CriteriaImpl.list(CriteriaImpl.java:370)
at
org.grails.orm.hibernate.query.AbstractHibernateQuery.listForCriteria(AbstractHibernateQuery.java:827)
at
org.grails.orm.hibernate.query.AbstractHibernateQuery.list(AbstractHibernateQuery.java:817)
at grails.gorm.PagedResultList.<init>(PagedResultList.java:50)
at
grails.gorm.DetachedCriteria$_list_closure2.doCall(DetachedCriteria.groovy:141)
...
I can provide a test project to reproduce the case if you need it
Gianluca
Gianluca Sartori
--
https://dueuno.com
On Mon, 20 Apr 2026 at 22:22, James Fredley <[email protected]> wrote:
> Hi Gianluca,
> Thank you - this is exactly the pattern I was hoping to see laid out
> end-to-end. A few thoughts and one follow-up question.
>
> 1) Your shape is effectively the real-world case for PR #15447
> (apache/grails-core):
> def query = TWorkAssignment.where {}
> if (filterParams.containsKey('id')) query = query.where { id ==
> filterParams.id }
> if (filterParams.containsKey('workPackage')) query = query.where {
> workPackage.id == filterParams.workPackage }
>
> Prior to that fix, DetachedCriteriaTransformer only visited
> DeclarationExpression, so every query = query.where { ... } re-assignment
> was silently skipped by the AST transform. At runtime the untransformed
> closures evaluated to non-mutating clones and buildQuery would have
> returned every TWorkAssignment regardless of filterParams. Adding
> visitBinaryExpression fixed that. Your reply confirms the fix covers the
> canonical "empty initial where + chained reassignment under if-guards"
> idiom, which is good to see.
> 2) One specific question on the alias line:
> if (filterParams.containsKey('workPackage.code')) query = query.where {
> def wp = workPackage
> wp.code == filterParams.'workPackage.code'
> }
> The GORM reference documents the explicit-alias idiom (def wp =
> workPackage) primarily for sorting/projection (.list(sort: 'wp.code')), but
> you are using it here purely for a filter. Is there a specific case where
> the direct form:
> workPackage.code == filterParams.'workPackage.code'
> misbehaves, and the aliased form rescues it? Or is this stylistic /
> defensive?
> If you have hit a real transformer or scope issue with the direct form,
> would you be willing to open an issue on apache/grails-core (or a PR if you
> already have a fix in mind)? It would be useful to track it alongside
> #15447/#15448 rather than leave it as tribal knowledge.
> If it is stylistic rather than a bug, would you consider contributing this
> pattern directly to the Grails documentation - specifically the GORM "Where
> Queries" section (
> https://grails.apache.org/docs/latest/guide/GORM.html#whereQueries)? The
> existing docs cover basic property comparison and associations but do not
> describe composing conditional filters via chained re-assignment, the
> flat-map-with-dotted-keys convention, or the aliased form for
> nested-property filtering. From your Medium article and this reply, the
> rules that emerge are:
> - @CompileStatic on the class, @CompileDynamic on the buildQuery method
> - Accept a Map filterParams; do not name local variables after domain
> properties
> - Start with an empty where {} and chain conditional query = query.where
> { ... } re-assignments
> - Flat-map-with-dotted-keys for nested filters:
> filterParams.'workPackage.code'
> - FK shortcut for simple association filters: workPackage.id ==
> filterParams.workPackage
> - Aliased association access for nested-property filters: def wp =
> workPackage; wp.code == ...
>
>
> James
>
> On 2026/04/20 10:42:29 Gianluca Sartori wrote:
> > Hi James,
> >
> > this is the query:
> >
> > @CompileDynamic
> > private DetachedCriteria<TWorkAssignment> buildQuery(Map
> filterParams) {
> > def query = TWorkAssignment.where {}
> >
> > if (filterParams.containsKey('id')) query = query.where { id
> > == filterParams.id }
> > if (filterParams.containsKey('workPackage')) query =
> > query.where { workPackage.id == filterParams.workPackage }
> > if (filterParams.containsKey('workPackage.code')) query =
> query.where {
> > def wp = workPackage
> > wp.code == filterParams.'workPackage.code'
> > }
> >
> > return query
> > }
> >
> >
> > Thank you for your time,
> > Gianluca
> >
> > Gianluca Sartori
> > --
> > https://dueuno.com
> >
> >
> > On Sat, 18 Apr 2026 at 02:43, James Fredley <[email protected]>
> wrote:
> >
> > > Can you share buildQuery? The def o1 = owner AST trick requires the
> > > literal Domain.where { ... } form; if your builder composes criteria
> > > programmatically, the alias won't be registered and the sort will
> silently
> > > drop.
> > >
> > > James
> > >
> > > On 2026/04/16 13:31:10 Gianluca Sartori wrote:
> > > > Hi folks,
> > > >
> > > > do you have evidence that the where query aliases are working for
> > > sorting?
> > > > It does not seem to work in my code.
> > > >
> > > > The only difference I see is that I sort multiple columns with
> something
> > > > like:
> > > >
> > > > def query = buildQuery(filterParams)
> > > > return query.list(sort: ['o1.firstName': 'desc'])
> > > >
> > > >
> > > >
> > > > >>>
> > > > 7.4.6. Query Aliases and Sorting
> > > >
> > > > If you define a query for an association an alias is automatically
> > > > generated for the query. For example the following query:
> > > >
> > > > def query = Pet.where {
> > > > owner.firstName == "Fred"
> > > > }
> > > >
> > > > Will generate an alias for the owner association such as
> owner_alias_0.
> > > > These generated aliases are fine for most cases, but are not useful
> if
> > > you
> > > > want to later sort or use a projection on the results. For example
> the
> > > > following query will fail:
> > > >
> > > > // fails because a dynamic alias is used
> > > > Pet.where {
> > > > owner.firstName == "Fred"
> > > > }.list(sort:"owner.lastName")
> > > >
> > > > If you plan to sort the results then an explicit alias should be
> used and
> > > > these can be defined by simply declaring a variable in the where
> query:
> > > >
> > > > def query = Pet.where {
> > > > def o1 = owner
> > > > o1.firstName == "Fred"
> > > > }.list(sort:'o1.lastName')
> > > >
> > > > Define an alias called o1
> > > > Use the alias in the query itself
> > > > Use the alias to sort the results
> > > >
> > > > By assigning the name of an association to a local variable it will
> > > > automatically become an alias usable within the query itself and
> also for
> > > > the purposes of sorting or projecting the results.
> > > >
> > > >
> > > >
> > > > Gianluca Sartori
> > > > --
> > > > https://dueuno.com
> > > >
> > >
> >
>