diff --git a/core/api/src/main/java/com/blazebit/persistence/spi/DbmsDialect.java b/core/api/src/main/java/com/blazebit/persistence/spi/DbmsDialect.java index b991889fe5..fac442f00c 100644 --- a/core/api/src/main/java/com/blazebit/persistence/spi/DbmsDialect.java +++ b/core/api/src/main/java/com/blazebit/persistence/spi/DbmsDialect.java @@ -154,6 +154,14 @@ public interface DbmsDialect { * @since 1.5.0 */ public boolean supportsNestedCorrelations(); + + /** + * Returns true if the dbms supports correlations in the JOIN ON clause, false otherwise. + * + * @return Whether correlations are supported by the dbms in the JOIN ON clause + * @since 1.6.12 + */ + public boolean supportsCorrelationInJoinOnClause(); /** * Returns true if the dbms supports the with clause in modification queries, false otherwise. diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java index b21edd6e62..b526af16bb 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java @@ -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.INNER, result.baseNode.getEntityType(), rootAliasInfo, false); + rootAliasInfo.setJoinNode(joinNode); + rootNodes.add(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 { @@ -1958,6 +1963,9 @@ private boolean shouldEmulateEntityJoin(JoinNode node) { if (node.getJoinType() != JoinType.INNER) { return false; } + if (!mainQuery.dbmsDialect.supportsCorrelationInJoinOnClause() && node.isEntityJoinNode() && node.getAliasInfo().getAliasOwner() != node.getParent().getAliasInfo().getAliasOwner()) { + return true; + } // in Hibernate < 5.1, we weren't able to refer to non-driving table aliases in the ON clause which can be worked around by emulating through a cross join if (!mainQuery.jpaProvider.supportsNonDrivingAliasInOnClause()) { // But this only works when the parent join node has no RIGHT or FULL joins @@ -2031,6 +2039,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; diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/dialect/DefaultDbmsDialect.java b/core/impl/src/main/java/com/blazebit/persistence/impl/dialect/DefaultDbmsDialect.java index 1b3b7f2d06..fdd59d263b 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/dialect/DefaultDbmsDialect.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/dialect/DefaultDbmsDialect.java @@ -284,6 +284,11 @@ public boolean supportsNestedCorrelations() { return true; } + @Override + public boolean supportsCorrelationInJoinOnClause() { + return true; + } + protected String getWindowFunctionDummyOrderBy() { return null; } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/dialect/H2DbmsDialect.java b/core/impl/src/main/java/com/blazebit/persistence/impl/dialect/H2DbmsDialect.java index 59f4611ad1..b962a58794 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/dialect/H2DbmsDialect.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/dialect/H2DbmsDialect.java @@ -153,4 +153,11 @@ public Character getDefaultEscapeCharacter() { public boolean supportsArbitraryLengthMultiset() { return true; } + + @Override + public boolean supportsCorrelationInJoinOnClause() { + // It's not possible to do `from Document d where exists (select 1 from Person p join Document d2 on d = d2)` + // i.e. refer to the join alias of the outer query from the ON condition within a subquery + return false; + } } diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/DelegatingDbmsDialect.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/DelegatingDbmsDialect.java index fc9fe48659..4ad4d77fc0 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/DelegatingDbmsDialect.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/DelegatingDbmsDialect.java @@ -112,6 +112,11 @@ public boolean supportsNestedCorrelations() { return delegate.supportsNestedCorrelations(); } + @Override + public boolean supportsCorrelationInJoinOnClause() { + return delegate.supportsCorrelationInJoinOnClause(); + } + @Override public boolean supportsWithClauseInModificationQuery() { return delegate.supportsWithClauseInModificationQuery(); diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/SubqueryTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/SubqueryTest.java index e336d1b4bb..06d67859cb 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/SubqueryTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/SubqueryTest.java @@ -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(); } @@ -345,7 +351,14 @@ public void testSubqueryCorrelatesArrayExpression() { .from("Document[_ MEMBER OF d.owner.ownedDocuments AND LENGTH(d.owner.name) > 0]", "dSub") .where("dSub").notEqExpression("d") .end(); - String expectedQuery = "SELECT d FROM Document d WHERE EXISTS (SELECT 1 FROM Document dSub, Document d_owner_base JOIN d_owner_base.owner owner_1 WHERE dSub MEMBER OF owner_1.ownedDocuments AND LENGTH(owner_1.name) > 0 AND d.id = d_owner_base.id AND dSub <> d)"; + + String expectedSubQuery; + if (jpaProvider.supportsEntityJoin()) { + expectedSubQuery = "SELECT 1 FROM Document dSub JOIN Document d_owner_base" + onClause("d.id = d_owner_base.id") + " JOIN d_owner_base.owner owner_1 WHERE dSub MEMBER OF owner_1.ownedDocuments AND LENGTH(owner_1.name) > 0 AND dSub <> d"; + } else { + expectedSubQuery = "SELECT 1 FROM Document dSub, Document d_owner_base JOIN d_owner_base.owner owner_1 WHERE dSub MEMBER OF owner_1.ownedDocuments AND LENGTH(owner_1.name) > 0 AND d.id = d_owner_base.id AND dSub <> d"; + } + String expectedQuery = "SELECT d FROM Document d WHERE EXISTS (" + expectedSubQuery + ")"; assertEquals(expectedQuery, crit.getQueryString()); crit.getResultList(); } @@ -359,7 +372,14 @@ public void testSubqueryCorrelatesArrayExpressionEntityEqual() { .from("Document[_ MEMBER OF d.owner.ownedDocuments AND LENGTH(d.owner.name) > 0]", "dSub") .where("dSub").notEqExpression("d") .end(); - String expectedQuery = "SELECT d FROM Document d WHERE EXISTS (SELECT 1 FROM Document dSub, Document d_owner_base JOIN d_owner_base.owner owner_1 WHERE dSub MEMBER OF owner_1.ownedDocuments AND LENGTH(owner_1.name) > 0 AND d = d_owner_base AND dSub <> d)"; + + String expectedSubQuery; + if (jpaProvider.supportsEntityJoin()) { + expectedSubQuery = "SELECT 1 FROM Document dSub JOIN Document d_owner_base" + onClause("d = d_owner_base") + " JOIN d_owner_base.owner owner_1 WHERE dSub MEMBER OF owner_1.ownedDocuments AND LENGTH(owner_1.name) > 0 AND dSub <> d"; + } else { + expectedSubQuery = "SELECT 1 FROM Document dSub, Document d_owner_base JOIN d_owner_base.owner owner_1 WHERE dSub MEMBER OF owner_1.ownedDocuments AND LENGTH(owner_1.name) > 0 AND d = d_owner_base AND dSub <> d"; + } + String expectedQuery = "SELECT d FROM Document d WHERE EXISTS (" + expectedSubQuery + ")"; assertEquals(expectedQuery, crit.getQueryString()); crit.getResultList(); } @@ -494,11 +514,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(); } @@ -536,7 +565,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()); @@ -553,11 +587,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(); } @@ -573,12 +616,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(); } diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TreatTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TreatTest.java index 7a2a245e10..414d101912 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TreatTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/TreatTest.java @@ -252,17 +252,22 @@ public void implicitJoinTreatedImplicitCorrelation() { .select("1") .where("TREAT(p.children.container.child AS PolymorphicSub1).id").eqExpression("sub1.id") .end(); - assertEquals( - "SELECT p FROM PolymorphicBase p " + - "WHERE EXISTS (" + - "SELECT 1 FROM PolymorphicSub1 sub1, PolymorphicBase p_children_base " + + String expectedSubQuery; + if (jpaProvider.supportsEntityJoin()) { + expectedSubQuery = "SELECT 1 FROM PolymorphicSub1 sub1 JOIN PolymorphicBase p_children_base" + onClause("p.id = p_children_base.id") + " " + + "LEFT JOIN p_children_base.children children_1 " + + "LEFT JOIN children_1.container container_1 " + + "LEFT JOIN container_1.child child_1 " + + "WHERE " + treatRoot("child_1", PolymorphicSub1.class, "id") + " = sub1.id"; + } else { + expectedSubQuery = "SELECT 1 FROM PolymorphicSub1 sub1, PolymorphicBase p_children_base " + "LEFT JOIN p_children_base.children children_1 " + "LEFT JOIN children_1.container container_1 " + "LEFT JOIN container_1.child child_1 " + "WHERE p.id = p_children_base.id " + - "AND " + treatRoot("child_1", PolymorphicSub1.class, "id") + " = sub1.id)", - crit.getQueryString() - ); + "AND " + treatRoot("child_1", PolymorphicSub1.class, "id") + " = sub1.id"; + } + assertEquals("SELECT p FROM PolymorphicBase p WHERE EXISTS (" + expectedSubQuery + ")", crit.getQueryString()); crit.getResultList(); } } diff --git a/core/testsuite/src/test/resources/logging.properties b/core/testsuite/src/test/resources/logging.properties index f307bc07b8..7c6d881baa 100644 --- a/core/testsuite/src/test/resources/logging.properties +++ b/core/testsuite/src/test/resources/logging.properties @@ -21,7 +21,7 @@ handlers = java.util.logging.ConsoleHandler org.hibernate.level = SEVERE org.hibernate.tool.hbm2ddl.level = OFF org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl.level = ALL -#org.hibernate.SQL.level = ALL +org.hibernate.SQL.level = ALL #org.hibernate.type.descriptor.sql.level = ALL #org.hibernate.tool.hbm2ddl.level = ALL #org.hibernate.pretty.level = ALL diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/limit/LimitOneToManyTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/limit/LimitOneToManyTest.java new file mode 100644 index 0000000000..c02d748cf2 --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/limit/LimitOneToManyTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2014 - 2024 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 paginatedCriteriaBuilder = evm.applySetting( + EntityViewSetting.create(PersonView.class, 0, 1), + cbf.create(em, Person.class, "p").orderByDesc("p.id") + ); + List 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(); + + } +}