HHH-15868 Added `crossJoin` methods and logic

This commit is contained in:
Marco Belladelli 2022-12-20 17:34:07 +01:00 committed by Christian Beikov
parent 977587dd67
commit 2c2ea7163b
6 changed files with 155 additions and 12 deletions

View File

@ -0,0 +1,16 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.criteria;
import org.hibernate.Incubating;
/**
* @author Marco Belladelli
*/
@Incubating
public interface JpaCrossJoin<T> extends JpaFrom<T, T> {
}

View File

@ -10,13 +10,8 @@ import org.hibernate.Incubating;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.sqm.tree.SqmJoinType;
import jakarta.persistence.criteria.CollectionJoin;
import jakarta.persistence.criteria.From;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.ListJoin;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.SetJoin;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.CollectionAttribute;
import jakarta.persistence.metamodel.ListAttribute;
@ -62,6 +57,12 @@ public interface JpaFrom<O,T> extends JpaPath<T>, JpaFetchParent<O,T>, From<O,T>
@Incubating
<X> JpaJoinedFrom<?, X> join(JpaCteCriteria<X> cte, SqmJoinType joinType);
@Incubating
<X> JpaCrossJoin<X> crossJoin(Class<X> entityJavaType);
@Incubating
<X> JpaCrossJoin<X> crossJoin(EntityDomainType<X> entity);
// Covariant overrides
@Override

View File

@ -24,6 +24,7 @@ import org.hibernate.metamodel.model.domain.MapPersistentAttribute;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.metamodel.model.domain.SetPersistentAttribute;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.query.criteria.JpaCrossJoin;
import org.hibernate.query.criteria.JpaCteCriteria;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaJoinedFrom;
@ -622,6 +623,19 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
}
}
@Override
public <X> JpaCrossJoin<X> crossJoin(Class<X> entityJavaType) {
return crossJoin( nodeBuilder().getDomainModel().entity( entityJavaType ) );
}
@Override
public <X> JpaCrossJoin<X> crossJoin(EntityDomainType<X> entity) {
final SqmCrossJoin<X> crossJoin = new SqmCrossJoin<>( entity, null, findRoot() );
// noinspection unchecked
addSqmJoin( (SqmJoin<T, ?>) crossJoin );
return crossJoin;
}
@Override
public Set<Fetch<T, ?>> getFetches() {
//noinspection unchecked

View File

@ -7,6 +7,7 @@
package org.hibernate.query.sqm.tree.from;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.criteria.JpaCrossJoin;
import org.hibernate.spi.NavigablePath;
import org.hibernate.query.PathException;
import org.hibernate.query.hql.spi.SqmCreationProcessingState;
@ -24,7 +25,7 @@ import static org.hibernate.query.sqm.spi.SqmCreationHelper.buildRootNavigablePa
/**
* @author Steve Ebersole
*/
public class SqmCrossJoin<T> extends AbstractSqmFrom<T, T> implements SqmJoin<T, T> {
public class SqmCrossJoin<T> extends AbstractSqmFrom<T, T> implements JpaCrossJoin<T>, SqmJoin<T, T> {
private final SqmRoot<?> sqmRoot;
public SqmCrossJoin(

View File

@ -0,0 +1,110 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.query.criteria;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCrossJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaQuery;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author Marco Belladelli
*/
@DomainModel(annotatedClasses = EntityOfBasics.class)
@SessionFactory
public class CriteriaCrossJoinTest {
@BeforeEach
public void setUp(SessionFactoryScope scope) {
scope.inTransaction( em -> {
Date now = new Date();
EntityOfBasics entity1 = new EntityOfBasics();
entity1.setId( 1 );
entity1.setTheString( "5" );
entity1.setTheInt( 5 );
entity1.setTheInteger( -1 );
entity1.setTheDouble( 1.0 );
entity1.setTheDate( now );
entity1.setTheLocalDateTime( LocalDateTime.now() );
entity1.setTheBoolean( true );
em.persist( entity1 );
EntityOfBasics entity2 = new EntityOfBasics();
entity2.setId( 2 );
entity2.setTheString( "6" );
entity2.setTheInt( 6 );
entity2.setTheInteger( -2 );
entity2.setTheDouble( 6.0 );
entity2.setTheBoolean( true );
em.persist( entity2 );
EntityOfBasics entity3 = new EntityOfBasics();
entity3.setId( 3 );
entity3.setTheString( "7" );
entity3.setTheInt( 7 );
entity3.setTheInteger( 3 );
entity3.setTheDouble( 7.0 );
entity3.setTheBoolean( false );
entity3.setTheDate( new Date( now.getTime() + 200000L ) );
em.persist( entity3 );
EntityOfBasics entity4 = new EntityOfBasics();
entity4.setId( 4 );
entity4.setTheString( "thirteen" );
entity4.setTheInt( 13 );
entity4.setTheInteger( 4 );
entity4.setTheDouble( 13.0 );
entity4.setTheBoolean( false );
entity4.setTheDate( new Date( now.getTime() + 300000L ) );
em.persist( entity4 );
EntityOfBasics entity5 = new EntityOfBasics();
entity5.setId( 5 );
entity5.setTheString( "5" );
entity5.setTheInt( 5 );
entity5.setTheInteger( 5 );
entity5.setTheDouble( 9.0 );
entity5.setTheBoolean( false );
em.persist( entity5 );
} );
}
@AfterEach
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction( session -> session.createMutationQuery( "delete from EntityOfBasics" ).executeUpdate() );
}
@Test
public void testSimpleCrossJoin(SessionFactoryScope scope) {
scope.inTransaction( session -> {
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
JpaRoot<EntityOfBasics> from = (JpaRoot<EntityOfBasics>) query.from( EntityOfBasics.class );
JpaCrossJoin<EntityOfBasics> crossJoin = from.crossJoin( EntityOfBasics.class );
query.multiselect( from.get( "id" ), crossJoin.get( "id" ) ).where( cb.gt( crossJoin.get( "theInt" ), 5 ) );
List<Tuple> resultList = session.createQuery( query ).getResultList();
assertEquals( 15, resultList.size() );
} );
}
}

View File

@ -15,7 +15,9 @@ import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.PostgresPlusDialect;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCrossJoin;
import org.hibernate.query.criteria.JpaExpression;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaWindow;
import org.hibernate.query.sqm.NullPrecedence;
import org.hibernate.query.sqm.SortOrder;
@ -309,17 +311,16 @@ public class CriteriaOrderedSetAggregateTest {
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsHypotheticalSetFunctions.class)
@RequiresDialect(H2Dialect.class)
public void testHypotheticalSetRankWithGroupByHavingOrderByLimit(SessionFactoryScope scope) {
// note : cross joins are not supported in criteria and this query structure causes problems with many dbs
scope.inTransaction( session -> {
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Tuple> cr = cb.createQuery( Tuple.class );
Root<EntityOfBasics> root1 = cr.from( EntityOfBasics.class );
Root<EntityOfBasics> root2 = cr.from( EntityOfBasics.class );
JpaRoot<EntityOfBasics> e1 = (JpaRoot<EntityOfBasics>) cr.from( EntityOfBasics.class );
JpaCrossJoin<EntityOfBasics> e2 = e1.crossJoin( EntityOfBasics.class );
JpaExpression<Long> function = cb.rank( cb.asc( root1.get( "theInt" ) ), cb.literal( 5 ) );
JpaExpression<Long> function = cb.rank( cb.asc( e1.get( "theInt" ) ), cb.literal( 5 ) );
cr.multiselect( root2.get( "id" ), function )
.groupBy( root2.get( "id" ) ).having( cb.gt( root2.get( "id" ), cb.literal( 1 ) ) )
cr.multiselect( e2.get( "id" ), function )
.groupBy( e2.get( "id" ) ).having( cb.gt( e2.get( "id" ), cb.literal( 1 ) ) )
.orderBy( cb.asc( cb.literal( 1 ) ), cb.asc( cb.literal( 2 ) ) );
List<Tuple> resultList = session.createQuery( cr ).setFirstResult( 1 ).getResultList();