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

HHH-13058 Fix issue left join root cannot be replaced by correlated parent in subquery #3528

Merged
merged 1 commit into from Sep 3, 2020

Conversation

NathanQingyangXu
Copy link
Contributor

@NathanQingyangXu NathanQingyangXu commented Aug 26, 2020

https://hibernate.atlassian.net/browse/HHH-13058

The current correlated subquery ignores the first left join and ends up with 'inner join' instead invariably. So suppose for the below entities:

@Entity(name = "Task")
public class Task {
    @Id
    Long id;

    @ManyToOne
    Patient patient;
}

@Entity(name = "Patient")
public class Patient {
    @Id
    Long id;

    @ManyToOne
    Site site;
}

@Entity(name = "Site")
public class Site {
    @Id
    Long id;
}

Note that @ManyToOne's optional is true by default.

the following Criteria would skip the Task without Patient:

final Subquery<Task> subquery = outerQuery.subquery( Task.class );
final Root<Task> subtask = subquery.correlate( outerTask );
final From<Task, Patient> patient = subtask.join( Task_.patient, JoinType.LEFT );
final From<Patient, Site> site = patient.join( Patient_.site, JoinType.LEFT );
outerQuery.where(
    builder.exists(
        subquery.select( subtask )
            .where(
                builder.or(
                    patient.isNull(),
                    site.in( validSites )
                )
        )
    )
);

because internally the jpasql generated is:

select generatedAlias0 from Task as generatedAlias0 
where exists (
    select generatedAlias0 
    from generatedAlias0.patient as generatedAlias1 
        left join generatedAlias1.site as generatedAlias2 
    where ( generatedAlias1 is null ) or ( generatedAlias2 in (:param0, :param1) 
))

Note that generatedAlias1 is null can never be true for the left join from Task to Patient has been skipped by correlate parent's alias substitution; but given that Task's Patient is optional we should reserve the left join from Task to Patient.
The fix in this PR is to create the following jpasql instead:

select generatedAlias0 from Task as generatedAlias0 
where exists (
    select generatedAlias0 
    from Task as generatedAlias1 
        left join generatedAlias1.patient as generatedAlias1 
        left join generatedAlias1.site as generatedAlias2 
    where 
        generatedAlias1 = generatedAlias0 and (( generatedAlias1 is null ) or ( generatedAlias2 in (:param0, :param1))
))

The basic idea is to generate new alias for correlated root and add newAlias = oldAlias to the WHERE jpasql clause, thus reserving the LEFT JOIN semantic of the correlated root in subquery.
However, the old pattern still remains for performance reason and the above changes only apply when LEFT JOIN is involved.

@NathanQingyangXu NathanQingyangXu marked this pull request as ready for review August 27, 2020 19:41
@NathanQingyangXu NathanQingyangXu changed the title HHH-13058 Criteria API correlated subquery with outer join generates incorrect SQL HHH-13058 Criteria API correlated subquery with LEFT JOIN generates incorrect SQL Aug 27, 2020
@NathanQingyangXu
Copy link
Contributor Author

Note for v6 porting. The code changes might not be applicable to v6 and nontrivial v6 specific tweaking might be needed, but the testing case is invaluable.

@NathanQingyangXu NathanQingyangXu changed the title HHH-13058 Criteria API correlated subquery with LEFT JOIN generates incorrect SQL HHH-13058 HHH-14197 Correlated criteria subquery issue Aug 28, 2020
@NathanQingyangXu NathanQingyangXu changed the title HHH-13058 HHH-14197 Correlated criteria subquery issue HHH-13058 & HHH-14197 Correlated criteria subquery issue Aug 28, 2020
Copy link
Contributor

@beikov beikov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requires some further work as the implementation produces SQL queries that aren't as efficient as before.

@NathanQingyangXu NathanQingyangXu changed the title HHH-13058 & HHH-14197 Correlated criteria subquery issue HHH-13058 Fix issue left join root cannot be replaced by correlated parent in subquery Sep 1, 2020
Copy link
Contributor

@beikov beikov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good!

@beikov beikov merged commit 6365204 into hibernate:master Sep 3, 2020
@NathanQingyangXu NathanQingyangXu deleted the HHH-13058 branch September 3, 2020 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants