Anyone had any joy with polymorphic criteria queri...
# questions
p
Anyone had any joy with polymorphic criteria queries on subclassed domains? I have:
Copy code
class Grandparent {
    static hasMany = [ parents:Parent ]
}

// Joins the 'Child' instances to the 'Grandparent' instance.
class Mother extends Parent {
    static belongsTo = [ grandparent:Grandparent ] 
}

// The superclass that holds the 'Child' instances. 
class Parent {
    static hasMany = [ children:Child ] 
}

class Child {
    belongsTo = [
        mother:Parent
    ]
}
So to find all the Child instances that belong to a particular Grandparent (via the Mother), I assumed I could do:
Copy code
def c = Child.createCriteria()
def children = c.list {
    mother {
        grandparent {
            eq('id', 1L)  // Looking Grandparent[1].
        }
    }
}
But it keeps complaining about
No signature of method grandparent.call()
. I'm guessing it's because 'mother' is not an actual Hibernate table whereas 'Parent' is the actual table. But I don't have a 'parent' property in the Child... Should I be looking at aliases, to try and encourage the query to look at the right column? Any ideas?
j
Your query closure looks like the mother property in Child should contain a property named grandparent, but mother is a Parent, which doesn't have a grandparent property. Is that the issue? It is possible I am misunderstanding.
Does the same happen if you change
belongsTo=[mother:Parent}
to
belongsTo=[mother:Mother]
?
p
Hm. Let me try and think that through - I'm using 'Grandparent', 'Parent', etc. here as a way of articulating the actual problem (my classes aren't those exact terms).
j
As written, I don't expect this code to work.
p
Incidentally, I have managed to get it to work by changing it to:
Copy code
def c = Child.createCriteria()
def children = c.list {
    mother {
        eq('grandparent.id', 1L)  // Looking Grandparent[1].
    }
}
j
"mother" is of type Parent, which doesn't have a "grandparent.id".
I am glad it works, but I am surprised. Well done.
p
šŸ™‚
Thanks!
j
You are welcome.
@pbarker Are you using Hibernate or some non-relational impl?
p
Hibernate.
j
Thank you. I am puzzled. šŸ˜‰
p
I think I might still have a problem though - looks like my query actually needs to go through that subclassed domain to get further up the graph, to particular instance that owns multiple 'Grandparent', instances. I'll see if I can work it up into a clearer picture, using the actual domain classes (not my clumsy attempt at over-simplification).
g
@pbarker I do not know if you have tried Claude Sonnet 3.7, it is amazingly accurate on gorm query. I posted your code against Claude, not exactly sure, but you can give it a try:
class Grandparent {
static hasMany = [ parents:Parent ]
}
// Joins the 'Child' instances to the 'Grandparent' instance.
class Mother extends Parent {
static belongsTo = [ grandparent:Grandparent ]
}
// The superclass that holds the 'Child' instances.
class Parent {
static hasMany = [ children:Child ]
}
class Child {
belongsTo = [
mother:Parent
]
}
So to find all the Child instances that belong to a particular Grandparent (via the Mother), I assumed I could do:
def c = Child.createCriteria()
def children = c.list {
mother {
grandparent {
eq('id', 1L)  // Looking Grandparent[1].
}
}
}
I'll analyze the code snippets you've provided and the query issue you're encountering. The problem with your query is that the relationship structure in your domain model doesn't match the traversal path you're trying to use in the criteria query. Looking at your class definitions: 1.
Grandparent
has many
Parent
objects 2.
Mother
(which extends
Parent
) belongs to a
Grandparent
3.
Parent
has many
Child
objects 4.
Child
belongs to a
Parent
(specified as
mother
) The issue is that in your criteria query, you're trying to navigate from
Child
→
mother
→
grandparent
, but your domain model doesn't establish this complete path. The
Child
class only has a relationship with
Parent
(via the
mother
property), not with
Mother
specifically, and there's no direct connection from
Parent
to
Grandparent
. Here are a few approaches to fix this: Option 1: If
mother
is always a
Mother
object (which has the
grandparent
association), you could use a type check:
Copy code
groovy



def c = Child.createCriteria()
def children = c.list {
    mother {
        eq('class', Mother)
        grandparent {
            eq('id', 1L)
        }
    }
}
Option 2: Modify your domain model to better reflect the relationships:
Copy code
groovy



class Child {
    static belongsTo = [
        mother: Mother  // Change from Parent to Mother
    ]
}
Option 3: Use a join in your criteria query:
Copy code
groovy



def c = Child.createCriteria()
def children = c.list {
    createAlias('mother', 'm')
    createAlias('m.grandparent', 'g')
    eq('g.id', 1L)
}
Option 4: Use a more complex HQL query:
Copy code
groovy



def children = Child.executeQuery("""
    from Child c 
    where c.mother in (
        from Mother m where m.grandparent.id = :grandparentId
    )
""", [grandparentId: 1L])
The key issue is that your object model and the query path don't align correctly. The relationship from
Child
to
Grandparent
via
Mother
needs to be properly established in both the domain model and the query.
p
That's amazing, thank you! This was indeed my problem; it's an 'incomplete' graph under certain circumstances. I didn't realise I could use
eq('class', Mother)
to filter out the gaps so I'll definitely give that a try. But I'm currently having trouble with
createAlias()
. Not sure if this is a separate question but it looks to me like Hibernate's
CriteriaImpl.java
isn't creating the alias. My alias statements:
Copy code
createAlias('rfxQuestion', 'questionAlias', JoinType.LEFT_OUTER_JOIN)
createAlias('questionAlias.note', 'noteAlias', JoinType.LEFT_OUTER_JOIN)

or {
    ilike('name', filters.search)
    ilike('questionAlias.noteAlias.html', filters.search)
}
Trouble is, I'm getting
could not resolve property: questionAlias
. Stepping it through, it's hitting
createAlias
in
org.hibernate.internal.CriteraImpl.java
which looks like this:
Copy code
@Override
public Criteria createAlias(String associationPath, String alias, JoinType joinType) {
    new Subcriteria( this, associationPath, alias, joinType );
	return this;
}
Which isn't actually returning the new
Subcriteria
. I don't really class myself as a Java developer but this doesn't look right to me! I must be missing something...
@gaolei Your 'Claude' tip was a lifesaver! After a couple of iterations, here's the fix (in case anyone else finds this useful):
Copy code
createAlias('rfxQuestion', 'questionAlias', CriteriaSpecification.LEFT_JOIN)
createAlias('questionAlias.note', 'noteAlias', CriteriaSpecification.LEFT_JOIN)
or {
    ilike('name', filters.search)
    ilike('noteAlias.html', filters.search)
}
• Using
CriteriaSpecification
instead of
JoinType
(even though it's deprecated and returns a
JoinType
anyway...) • Only reference the last alias in the chain in the
ilike
statement. I was previously referencing the whole chain.
šŸ˜‚ 1
g
I found Claude 3.7 Sonnet is very good. It can generate the closest final code to our real use cases. Gemini 2.5 pro is a true bragging. Its code generation is dumb and clumsy. I would wonder from Grails 7. if we could introduce the LLM assistant feature to empower Grails. @James Fredley
p
If a lot of folks are using IntelliJ (I do) then the Gemini plugin works nicely in that (although I had mixed results with it - certainly not as accurate as Claude). Haven't looked to see if Claude can be used in IntelliJ instead but no reason why it shouldn't.
šŸ‘ 1
Most useful if Claude can see your entire project's codebase and solve prompted requests.
šŸ‘ 1
...without you having to explain the context each time.
Not sure how many company's would feel about their codebase being analysed by a 3rd party app, possibly in a different country. But then, perhaps it's the same as using GitHub, BitBucket, etc?
g
Here are my needs about LLM assistant from Grails, if Grails can provide the following functions: 1. SSE enabled Controller to talk to the front end. 2. A distributed cache to support ChatMemory 3. A queue based backend/front end conversation management. 4. A database storage(SQL or NoSQL) to store the chat history(memory) I think all these are pretty suitable for Grails frame work.
@pbarker I totally with you. I can see in a foreseen future, a private LLM hosting business can emerge
p
Guaranteeing code-security for all LLM analysis. Haven't looked but I'm guessing the big providers may already do that under their Enterprise solutions.
g
Yeah, but that way too expensive.
p
And where it's so expensive, there's usually an opportunity to be found...
šŸ‘ 1
g
I want something like $200/mon. I guess many doctors and lawyers they want that
p
Another way to look at it is how much of a human developer would a customer save by incorporating LLM into their dev cycle. If they can save themselves the cost of a single developer, they have a solid business case to spend at least half that cost on the LLM and still save money.
g
I agreed, there are many new business model look interesting. We should exam them and take advantage of them
šŸ‘ 1