Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Panache - load page with join fetch #26308

Closed
g-apparence opened this issue Jun 23, 2022 · 9 comments · Fixed by #38749
Closed

Panache - load page with join fetch #26308

g-apparence opened this issue Jun 23, 2022 · 9 comments · Fixed by #38749
Assignees
Labels
area/panache area/persistence OBSOLETE, DO NOT USE kind/bug Something isn't working
Milestone

Comments

@g-apparence
Copy link

g-apparence commented Jun 23, 2022

Describe the bug

Hi,
loading a page using panache works great.
Problem is when you try to fetch subcollection within a page loading like this :

@NamedQueries({
        @NamedQuery(name = "Videos.VideosByProjectAndStatus",
            query = "from videos v " +
                    "left join fetch v.audience a " +
                    "left join fetch v.survey sv " +
                    "left join fetch v.events ev " +
                    "left join fetch v.videoResource vr " +
                    "where v.project.uid = ?1"),
})

...

public static PageItems<VideoEntity> pageByProjectIdAndStatus(UUID id, List<VideoStatus> statuses,  int pageIndex, int pageSize) {
        var query = VideoEntity.find(
            "project.uid = ?1 and status in ?2",
            Sort.by("creationDate").descending(),
            id, statuses
        ).page(Page.of(pageIndex, pageSize));

        var pageCount = query.pageCount(); // <<--- this is what creates the error
        List<VideoEntity> items = query.list();
        return ImmutablePageItems.of(pageSize, pageCount - 1, pageIndex, items);
    }

Calling a request without the joins for the count query works.
Maybe we can make this a bit more friendly as join with pagination is a common case.

Expected behavior

You should be able to load a page with a fetch join.
Replacing lazy by EAGER works. (but I don't want to be eager on every request...)

Actual behavior

The error

Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=null,role=io.apparence.entities.VideoEntity.audience,tableName=video_audience,tableAlias=videoaudie1_,origin=videos videoentit0_,columns={videoentit0_.uid,className=io.apparence.entities.VideoAudienceEntity}}] [SELECT COUNT(*) from io.apparence.entities.VideoEntity v join fetch v.audience where v.project.uid = ?1]

How to Reproduce?

No response

Output of uname -a or ver

No response

Output of java -version

17

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.9.2.Final

Build tool (ie. output of mvnw --version or gradlew --version)

No response

Additional information

No response

@g-apparence g-apparence added the kind/bug Something isn't working label Jun 23, 2022
@quarkus-bot
Copy link

quarkus-bot bot commented Jun 23, 2022

/cc @FroMage, @loicmathieu

@geoand geoand added area/persistence OBSOLETE, DO NOT USE area/hibernate-orm Hibernate ORM labels Jun 23, 2022
@victor-h-costa
Copy link

victor-h-costa commented Jul 4, 2022

I've ran into this same issue today calling PanacheQuery.count(), although PanacheQuery.list() works just fine. In my case I can workaround it by removing " FETCH " from my query but then I will have a N + 1 query problem.

@FroMage
Copy link
Member

FroMage commented Jul 18, 2022

This looks like an ORM question @Sanne or @yrodiere

@yrodiere
Copy link
Member

yrodiere commented Jul 19, 2022

@FroMage As far as I understand, this is clearly a Panache issue, caused by the way Panache implements pageCount/count:

    public static String getCountQuery(String query) {
        // try to generate a good count query from the existing query
        Matcher selectMatcher = SELECT_PATTERN.matcher(query);
        String countQuery;
        if (selectMatcher.matches()) {
            // this one cannot be null
            String firstSelection = selectMatcher.group(1).trim();
            if (firstSelection.toLowerCase().startsWith("distinct ")) {
                // this one can be null
                String secondSelection = selectMatcher.group(2);
                // we can only count distinct single columns
                if (secondSelection != null && !secondSelection.trim().isEmpty()) {
                    throw new PanacheQueryException("Count query not supported for select query: " + query);
                }
                countQuery = "SELECT COUNT(" + firstSelection + ") " + selectMatcher.group(3);
            } else {
                // it's not distinct, forget the column list
                countQuery = "SELECT COUNT(*) " + selectMatcher.group(3);
            }
        } else if (FROM_PATTERN.matcher(query).matches()) {
            countQuery = "SELECT COUNT(*) " + query;
        } else {
            throw new PanacheQueryException("Count query not supported for select query: " + query);
        }

        // remove the order by clause
        String lcQuery = countQuery.toLowerCase();
        int orderByIndex = lcQuery.lastIndexOf(" order by ");
        if (orderByIndex != -1) {
            countQuery = countQuery.substring(0, orderByIndex);
        }
        return countQuery;
    }

That method removes the "SELECT" part of the query, but doesn't remove the "FETCH" clauses from the query. A "FETCH" clause without a corresponding "SELECT" clause doesn't make sense, hence the failure.

That's precisely what the exception is trying to explain:

Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=null,role=io.apparence.entities.VideoEntity.audience,tableName=video_audience,tableAlias=videoaudie1_,origin=videos videoentit0_,columns={videoentit0_.uid,className=io.apparence.entities.VideoAudienceEntity}}] [SELECT COUNT(*) from io.apparence.entities.VideoEntity v join fetch v.audience where v.project.uid = ?1]

@yrodiere yrodiere added area/persistence OBSOLETE, DO NOT USE and removed area/persistence OBSOLETE, DO NOT USE area/hibernate-orm Hibernate ORM labels Jul 19, 2022
@blazmrak
Copy link
Contributor

blazmrak commented Jun 16, 2023

What is the state of this? Pagination also works just for simple queries, as soon as you join one to many relation it returns too few rows. Is there a desire to have proper pagination implemented, by doing a SELECT DISTINCT id ... offset x limit y and only then SELECT ... WHERE ... AND id IN (...distinctIds)? The count implementation would then follow the same SELECT DISTINCT id ... to get the actual count.

@FroMage
Copy link
Member

FroMage commented Jun 19, 2023

Do you have any other example of join queries where it returns invalid count results? This would be useful for testing.

I guess we'd need to start with better query parsing :(

@blazmrak
Copy link
Contributor

From looking at the queries doing something like

SELECT u FROM User u LEFT JOIN FETCH u.recipes r

would return only first X rows even if I want first X users right?

@beiertu-mms
Copy link

Any updates on this? We're currently using Quarkus v3.7.1 and this issue is still present.

@FroMage
Copy link
Member

FroMage commented Feb 12, 2024

I'm working on a fix

@FroMage FroMage self-assigned this Feb 12, 2024
FroMage added a commit to FroMage/quarkus that referenced this issue Feb 13, 2024
We do this by turning to the ORM HQLParser for non-trivial queries, but
only for them, because the parser is much more expensive than simple
string manipulation, so we keep the fast/easy logic.

Fixes quarkusio#26308
@quarkus-bot quarkus-bot bot added this to the 3.9 - main milestone Feb 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/panache area/persistence OBSOLETE, DO NOT USE kind/bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants