Alias XXX used for multiple from-clause elements error when executing query with 2 level join
Description
Seems there is a bug somewhere either in query parsing or joins processing in hibernate 6.1.7+ That code & query works fine in all prev. versions from hibernate 3 to 5 I have a named query: ``` <query>SELECT COUNT(adminPermission) FROM org.broadleafcommerce.openadmin.server.security.domain.AdminPermission adminPermission LEFT OUTER JOIN adminPermission.allChildPermissions childPermission LEFT OUTER JOIN childPermission.qualifiedEntities childQualifiedEntity LEFT OUTER JOIN adminPermission.qualifiedEntities qualifiedEntity LEFT OUTER JOIN adminPermission.allRoles role LEFT OUTER JOIN role.allUsers roleUser LEFT OUTER JOIN adminPermission.allUsers permissionUser WHERE (roleUser = :adminUser OR permissionUser = :adminUser) AND (adminPermission.type = :type OR adminPermission.type = 'ALL' OR childPermission.type = :type OR childPermission.type = 'ALL') AND (qualifiedEntity.ceilingEntityFullyQualifiedName = :ceilingEntity OR childQualifiedEntity.ceilingEntityFullyQualifiedName = :ceilingEntity) </query> ``` There OneToMany and ManyToMany through JoinTable asossiations it this is important I debugged a bit and see that it processes condition (roleUser = :adminUser OR permissionUser = :adminUser) and finds a join LEFT OUTER JOIN role.allUsers roleUser so it finds alias "roleUser", logic goes into org.hibernate.query.hql.internal.QuerySplitter.UnmappedPolymorphismReplacer#visitSetJoin where it does: final SqmSetJoin copy = new SqmSetJoin<>( findSqmFromCopy( join.getLhs() ), so join.getLhs() will return another join LEFT OUTER JOIN adminPermission.allRoles role so it finds alias "role" And in org.hibernate.query.hql.internal.QuerySplitter.UnmappedPolymorphismReplacer#findSqmFromCopy it does return (X) sqmFrom.accept( this ); So it will do a recursive call that will eventually come to org.hibernate.query.hql.internal.QuerySplitter.UnmappedPolymorphismReplacer#visitSetJoin again, where it will process join with alias "role" and in org.hibernate.query.hql.internal.QuerySplitter.UnmappedPolymorphismReplacer#visitJoins will register that alias, and also it will do join.visitSqmJoins( sqmJoin -> sqmJoin.accept( this ) ); where "role" join will reference "roleUser" join via joins.forEach( consumer ); in org.hibernate.query.sqm.tree.domain.AbstractSqmFrom#visitSqmJoins
it will process it and also registers via org.hibernate.query.hql.internal.SqmPathRegistryImpl#register(org.hibernate.query.sqm.tree.domain.SqmPath<?>) so now registry map knows about "role" and "roleUser" joins and recursion ends and it eventually continues to process "roleUse" join - where we start recursion... and it again will attempt to register org.hibernate.query.hql.internal.SqmPathRegistryImpl#register(org.hibernate.query.sqm.tree.domain.SqmPath<?>) where it fails with exception... So there is something wrong on how "multi-level joins" are handled.
Attachments
1
Activity
Show:
Oleksii Miroshnyk May 11, 2023 at 9:48 AM
Added a PR
Marco Belladelli May 11, 2023 at 9:35 AM
Original post: .
Oleksii Miroshnyk May 11, 2023 at 9:29 AM
attached project with test case.
Seems this is happening when you use interfaces. Without interfaces works fine
Seems there is a bug somewhere either in query parsing or joins processing in hibernate 6.1.7+
That code & query works fine in all prev. versions from hibernate 3 to 5
I have a named query:
```
<query>SELECT COUNT(adminPermission)
FROM org.broadleafcommerce.openadmin.server.security.domain.AdminPermission adminPermission
LEFT OUTER JOIN adminPermission.allChildPermissions childPermission
LEFT OUTER JOIN childPermission.qualifiedEntities childQualifiedEntity
LEFT OUTER JOIN adminPermission.qualifiedEntities qualifiedEntity
LEFT OUTER JOIN adminPermission.allRoles role
LEFT OUTER JOIN role.allUsers roleUser
LEFT OUTER JOIN adminPermission.allUsers permissionUser
WHERE (roleUser = :adminUser OR permissionUser = :adminUser) AND
(adminPermission.type = :type OR adminPermission.type = 'ALL' OR
childPermission.type = :type OR childPermission.type = 'ALL') AND
(qualifiedEntity.ceilingEntityFullyQualifiedName = :ceilingEntity OR childQualifiedEntity.ceilingEntityFullyQualifiedName = :ceilingEntity)
</query>
```
There OneToMany and ManyToMany through JoinTable asossiations it this is important
I debugged a bit and see that it processes condition (roleUser = :adminUser OR permissionUser = :adminUser)
and finds a join
LEFT OUTER JOIN role.allUsers roleUser
so it finds alias "roleUser", logic goes into
org.hibernate.query.hql.internal.QuerySplitter.UnmappedPolymorphismReplacer#visitSetJoin
where it does:
final SqmSetJoin copy = new SqmSetJoin<>(
findSqmFromCopy( join.getLhs() ),
so join.getLhs() will return another join
LEFT OUTER JOIN adminPermission.allRoles role
so it finds alias "role"
And in org.hibernate.query.hql.internal.QuerySplitter.UnmappedPolymorphismReplacer#findSqmFromCopy
it does
return (X) sqmFrom.accept( this );
So it will do a recursive call that will eventually come to org.hibernate.query.hql.internal.QuerySplitter.UnmappedPolymorphismReplacer#visitSetJoin again, where it will process join with alias "role" and in
org.hibernate.query.hql.internal.QuerySplitter.UnmappedPolymorphismReplacer#visitJoins
will register that alias, and also it will do
join.visitSqmJoins(
sqmJoin ->
sqmJoin.accept( this )
);
where "role" join will reference "roleUser" join via joins.forEach( consumer ); in
org.hibernate.query.sqm.tree.domain.AbstractSqmFrom#visitSqmJoins
it will process it and also registers via org.hibernate.query.hql.internal.SqmPathRegistryImpl#register(org.hibernate.query.sqm.tree.domain.SqmPath<?>)
so now registry map knows about "role" and "roleUser" joins and recursion ends and it eventually continues to process "roleUse" join - where we start recursion... and it again will attempt to register
org.hibernate.query.hql.internal.SqmPathRegistryImpl#register(org.hibernate.query.sqm.tree.domain.SqmPath<?>)
where it fails with exception... So there is something wrong on how "multi-level joins" are handled.