Yes, please create an issue with test project.
James
On 2026/04/21 08:01:32 Gianluca Sartori wrote:
> 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
> > > > >
> > > >
> > >
> >
>