Skip to content

Commit

Permalink
[#1811] Don't generate root for intermediate from node of correlated …
Browse files Browse the repository at this point in the history
…join path
  • Loading branch information
beikov committed Oct 5, 2023
1 parent 2854cd3 commit cf1caf3
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1017,15 +1017,20 @@ private JoinResult correlate(JoinResult result, String rootAlias, Expression cor
if (!aliasManager.isAliasAvailable(alias)) {
alias = aliasManager.generateRootAlias(alias);
}
String baseAlias = addRoot(result.baseNode.getEntityType(), alias, false);
JoinNode joinNode = ((JoinAliasInfo) aliasManager.getAliasInfo(baseAlias)).getJoinNode();
joinNode.getAliasInfo().setImplicit(true);
Predicate correlationPredicate = expressionFactory.createBooleanExpression(createCorrelationPredicate(result.baseNode.getEntityType(), result.baseNode.getAliasExpression(), baseAlias), false);
JoinAliasInfo rootAliasInfo = new JoinAliasInfo(alias, alias, true, true, aliasManager);
JoinNode joinNode = JoinNode.createEntityJoinNode(result.baseNode, JoinType.LEFT, result.baseNode.getEntityType(), rootAliasInfo, false);
result.baseNode.addEntityJoin(joinNode);
rootAliasInfo.setJoinNode(joinNode);
explicitJoinNodes.add(joinNode);
// register root alias in aliasManager
aliasManager.registerAliasInfo(rootAliasInfo);

Predicate correlationPredicate = expressionFactory.createBooleanExpression(createCorrelationPredicate(result.baseNode.getEntityType(), result.baseNode.getAliasExpression(), alias), false);
correlationPredicate.accept(joinVisitor);
joinNode.setOnPredicate(new CompoundPredicate(CompoundPredicate.BooleanOperator.AND, correlationPredicate));
if (implicit || !(correlatedAttributeExpr instanceof ArrayExpression)) {
PathExpression pathExpression = new PathExpression();
pathExpression.getExpressions().add(new PropertyExpression(baseAlias));
pathExpression.getExpressions().add(new PropertyExpression(alias));
if (correlatedAttributeExpr instanceof PathExpression) {
pathExpression.getExpressions().addAll(((PathExpression) correlatedAttributeExpr).getExpressions());
} else {
Expand Down Expand Up @@ -2031,6 +2036,14 @@ private boolean renderCorrelationJoinPath(StringBuilder sb, JoinNode joinBase, J
}
whereConjuncts.add(whereSb.toString());
return true;
} else if (!externalRepresentation && !node.isLateral()) {
sb.append(joinBase.getEntityType().getName());
sb.append(" _synthetic_");
sb.append(node.getAlias());
sb.append(" JOIN _synthetic_");
sb.append(node.getAlias());
sb.append('.').append(correlationPath);
return true;
}
} else {
boolean renderAlias = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,13 @@ public void testSubqueryImplicitCorrelate() {
.where("d.owner.friend.name").isNotNull()
.where("d.owner.defaultLanguage").isNotNull()
.end();
String expectedQuery = "SELECT d FROM Document d WHERE EXISTS (SELECT 1 FROM Person p, Document d_owner_base JOIN d_owner_base.owner owner_1 LEFT JOIN owner_1.friend friend_1 WHERE d.id = d_owner_base.id AND friend_1.name IS NOT NULL AND owner_1.defaultLanguage IS NOT NULL)";
String expectedSubQuery;
if (jpaProvider.supportsEntityJoin()) {
expectedSubQuery = "SELECT 1 FROM Person p JOIN Document d_owner_base ON (d.id = d_owner_base.id) JOIN d_owner_base.owner owner_1 LEFT JOIN owner_1.friend friend_1 WHERE friend_1.name IS NOT NULL AND owner_1.defaultLanguage IS NOT NULL";
} else {
expectedSubQuery = "SELECT 1 FROM Person p, Document d_owner_base JOIN d_owner_base.owner owner_1 LEFT JOIN owner_1.friend friend_1 WHERE d.id = d_owner_base.id AND friend_1.name IS NOT NULL AND owner_1.defaultLanguage IS NOT NULL";
}
String expectedQuery = "SELECT d FROM Document d WHERE EXISTS (" + expectedSubQuery + ")";
assertEquals(expectedQuery, crit.getQueryString());
crit.getResultList();
}
Expand Down Expand Up @@ -494,11 +500,20 @@ public void testSubqueryCollectionAccessAddsJoin() {
.where("LENGTH(d.partners.localized[1])").gt(1)
.end()
.like().value("%dld").noEscape();
String expectedQuery = "SELECT d FROM Document d"
+ " WHERE (SELECT p.name FROM Person p, Document d_partners_base " +
String expectedSubQuery;
if (jpaProvider.supportsEntityJoin()) {
expectedSubQuery = "SELECT p.name FROM Person p JOIN Document d_partners_base ON (d.id = d_partners_base.id) " +
"LEFT JOIN d_partners_base.partners partners_1 " +
"LEFT JOIN partners_1.localized localized_1_1" + onClause("KEY(localized_1_1) = 1") +
" WHERE LENGTH("+ joinAliasValue("localized_1_1") + ") > :param_0";
} else {
expectedSubQuery = "SELECT p.name FROM Person p, Document d_partners_base " +
"LEFT JOIN d_partners_base.partners partners_1 " +
"LEFT JOIN partners_1.localized localized_1_1" + onClause("KEY(localized_1_1) = 1") +
" WHERE d.id = d_partners_base.id AND LENGTH("+ joinAliasValue("localized_1_1") + ") > :param_0) LIKE :param_1";
" WHERE d.id = d_partners_base.id AND LENGTH("+ joinAliasValue("localized_1_1") + ") > :param_0";
}
String expectedQuery = "SELECT d FROM Document d"
+ " WHERE (" + expectedSubQuery + ") LIKE :param_1";
assertEquals(expectedQuery, crit.getQueryString());
crit.getResultList();
}
Expand Down Expand Up @@ -536,7 +551,12 @@ public void testSubqueryAddsJoin() {
.groupBy("id")
.orderByAsc("localizedCount");

String expectedSubQuery = "ABS((SELECT COUNT(" + joinAliasValue("localized_1") + ") FROM Person p LEFT JOIN p.localized localized_1, Document d_contacts_base LEFT JOIN d_contacts_base.contacts contacts_1 WHERE d.id = d_contacts_base.id AND p.id = " + joinAliasValue("contacts_1", "id") + "))";
String expectedSubQuery;
if (jpaProvider.supportsEntityJoin()) {
expectedSubQuery = "ABS((SELECT COUNT(" + joinAliasValue("localized_1") + ") FROM Person p LEFT JOIN p.localized localized_1 JOIN Document d_contacts_base ON (d.id = d_contacts_base.id) LEFT JOIN d_contacts_base.contacts contacts_1 WHERE p.id = " + joinAliasValue("contacts_1", "id") + "))";
} else {
expectedSubQuery = "ABS((SELECT COUNT(" + joinAliasValue("localized_1") + ") FROM Person p LEFT JOIN p.localized localized_1, Document d_contacts_base LEFT JOIN d_contacts_base.contacts contacts_1 WHERE d.id = d_contacts_base.id AND p.id = " + joinAliasValue("contacts_1", "id") + "))";
}
String expectedQuery = "SELECT d.id, " + expectedSubQuery + " AS localizedCount "
+ "FROM Document d GROUP BY d.id ORDER BY localizedCount ASC";
assertEquals(expectedQuery, cb.getQueryString());
Expand All @@ -553,11 +573,20 @@ public void testSubqueryCollectionAccess() {
.end()
.like().value("%dld").noEscape();

String expectedQuery = "SELECT d FROM Document d"
+ " WHERE (SELECT p.name FROM Person p, Document d_partners_base " +
String expectedSubQuery;
if (jpaProvider.supportsEntityJoin()) {
expectedSubQuery = "SELECT p.name FROM Person p JOIN Document d_partners_base ON (d.id = d_partners_base.id) " +
"LEFT JOIN d_partners_base.partners partners_1 " +
"LEFT JOIN partners_1.localized localized_1_1" + onClause("KEY(localized_1_1) = 1") +
" WHERE d.id = d_partners_base.id AND LENGTH("+ joinAliasValue("localized_1_1") + ") > :param_0) LIKE :param_1";
" WHERE LENGTH("+ joinAliasValue("localized_1_1") + ") > :param_0";
} else {
expectedSubQuery = "SELECT p.name FROM Person p, Document d_partners_base " +
"LEFT JOIN d_partners_base.partners partners_1 " +
"LEFT JOIN partners_1.localized localized_1_1" + onClause("KEY(localized_1_1) = 1") +
" WHERE d.id = d_partners_base.id AND LENGTH("+ joinAliasValue("localized_1_1") + ") > :param_0";
}
String expectedQuery = "SELECT d FROM Document d"
+ " WHERE (" + expectedSubQuery + ") LIKE :param_1";
assertEquals(expectedQuery, crit.getQueryString());
crit.getResultList();
}
Expand All @@ -573,12 +602,21 @@ public void testMultipleJoinPathSubqueryCollectionAccess() {
.end()
.like().value("%dld").noEscape();

String expectedSubQuery;
if (jpaProvider.supportsEntityJoin()) {
expectedSubQuery = "SELECT p.name FROM Person p JOIN Person d_partners_localized_base ON (partners_1.id = d_partners_localized_base.id) " +
"LEFT JOIN d_partners_localized_base.localized localized_1_1"
+ onClause("KEY(localized_1_1) = 1")
+ " WHERE LENGTH("+ joinAliasValue("localized_1_1") + ") > :param_0";
} else {
expectedSubQuery = "SELECT p.name FROM Person p, Person d_partners_localized_base LEFT JOIN d_partners_localized_base.localized localized_1_1"
+ onClause("KEY(localized_1_1) = 1")
+ " WHERE partners_1.id = d_partners_localized_base.id AND LENGTH("+ joinAliasValue("localized_1_1") + ") > :param_0";
}
String expectedQuery = "SELECT d FROM Document d "
+ "LEFT JOIN d.partners partners_1 "
+ "LEFT JOIN partners_1.localized l "
+ "WHERE (SELECT p.name FROM Person p, Person d_partners_localized_base LEFT JOIN d_partners_localized_base.localized localized_1_1"
+ onClause("KEY(localized_1_1) = 1")
+ " WHERE partners_1.id = d_partners_localized_base.id AND LENGTH("+ joinAliasValue("localized_1_1") + ") > :param_0) LIKE :param_1";
+ "WHERE (" + expectedSubQuery + ") LIKE :param_1";
assertEquals(expectedQuery, crit.getQueryString());
crit.getResultList();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright 2014 - 2023 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.blazebit.persistence.view.testsuite.limit;

import com.blazebit.persistence.PaginatedCriteriaBuilder;
import com.blazebit.persistence.testsuite.base.jpa.category.NoDatanucleus;
import com.blazebit.persistence.testsuite.base.jpa.category.NoEclipselink;
import com.blazebit.persistence.testsuite.base.jpa.category.NoH2;
import com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate42;
import com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate43;
import com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate50;
import com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate51;
import com.blazebit.persistence.testsuite.base.jpa.category.NoMySQLOld;
import com.blazebit.persistence.testsuite.base.jpa.category.NoOpenJPA;
import com.blazebit.persistence.testsuite.entity.Document;
import com.blazebit.persistence.testsuite.entity.Person;
import com.blazebit.persistence.testsuite.tx.TxVoidWork;
import com.blazebit.persistence.view.ConfigurationProperties;
import com.blazebit.persistence.view.EntityView;
import com.blazebit.persistence.view.EntityViewManager;
import com.blazebit.persistence.view.EntityViewSetting;
import com.blazebit.persistence.view.IdMapping;
import com.blazebit.persistence.view.Limit;
import com.blazebit.persistence.view.Mapping;
import com.blazebit.persistence.view.testsuite.AbstractEntityViewTest;
import com.blazebit.persistence.view.testsuite.limit.model.DocumentLimitView;
import com.blazebit.persistence.view.testsuite.limit.model.PersonLimitJoinExpressionView;
import com.blazebit.persistence.view.testsuite.limit.model.PersonLimitJoinView;
import com.blazebit.persistence.view.testsuite.limit.model.PersonLimitMultisetView;
import com.blazebit.persistence.view.testsuite.limit.model.PersonLimitSelectView;
import com.blazebit.persistence.view.testsuite.limit.model.PersonLimitSubselectView;
import com.blazebit.persistence.view.testsuite.limit.model.PersonLimitView;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import javax.persistence.EntityManager;
import java.util.List;

import static org.junit.Assert.assertEquals;

/**
*
* @author Christian Beikov
* @since 1.5.0
*/
public class LimitOneToManyTest extends AbstractEntityViewTest {

@Override
public void setUpOnce() {
cleanDatabase();
transactional(new TxVoidWork() {
@Override
public void work(EntityManager em) {
Person o1 = new Person("pers1");
Document doc1 = new Document("doc1", o1);
doc1.setAge(10);
Document doc2 = new Document("doc2", o1);
doc2.setAge(5);
Document doc3 = new Document("doc3", o1);
doc3.setAge(10);

em.persist(o1);
em.persist(doc1);
em.persist(doc2);
em.persist(doc3);
}
});
}

@Test
@Category({ NoMySQLOld.class, NoHibernate42.class, NoHibernate43.class, NoHibernate50.class, NoHibernate51.class, NoEclipselink.class, NoDatanucleus.class, NoOpenJPA.class })
// We need a left entity join for this so Hibernate < 5.1 can't be used
// MySQL before 8 didn't support lateral and also don't support correlated LIMIT subqueries in quantified predicates
// EclipseLink doesn't support subqueries in functions which is required for LIMIT
// Datanucleus fails because of a NPE?
// OpenJPA has no function support
public void testLimitJoin() {
EntityViewManager evm = build(SimplePersonView.class, PersonView.class);
PaginatedCriteriaBuilder<PersonView> paginatedCriteriaBuilder = evm.applySetting(
EntityViewSetting.create(PersonView.class, 0, 1),
cbf.create(em, Person.class, "p").orderByDesc("p.id")
);
List<? extends PersonView> list = paginatedCriteriaBuilder.getResultList();
assertEquals(1, list.size());
// assertEquals(1, list.get(0).getOwnedDocuments().size());
// assertEquals("doc2", list.get(0).getOwnedDocuments().get(0).getName());
}

@EntityView(Person.class)
interface SimplePersonView {
@IdMapping
Long getId();
}

@EntityView(Person.class)
interface PersonView extends SimplePersonView {
@Mapping("partnerDocument.contacts")
@Limit(limit = "1", order = "id desc")
SimplePersonView getBiggest();

}
}

0 comments on commit cf1caf3

Please sign in to comment.