HHH-15868 Added `crossJoin` methods and logic
This commit is contained in:
parent
977587dd67
commit
2c2ea7163b
|
@ -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> {
|
||||||
|
}
|
|
@ -10,13 +10,8 @@ import org.hibernate.Incubating;
|
||||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||||
import org.hibernate.query.sqm.tree.SqmJoinType;
|
import org.hibernate.query.sqm.tree.SqmJoinType;
|
||||||
|
|
||||||
import jakarta.persistence.criteria.CollectionJoin;
|
|
||||||
import jakarta.persistence.criteria.From;
|
import jakarta.persistence.criteria.From;
|
||||||
import jakarta.persistence.criteria.Join;
|
|
||||||
import jakarta.persistence.criteria.JoinType;
|
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.criteria.Subquery;
|
||||||
import jakarta.persistence.metamodel.CollectionAttribute;
|
import jakarta.persistence.metamodel.CollectionAttribute;
|
||||||
import jakarta.persistence.metamodel.ListAttribute;
|
import jakarta.persistence.metamodel.ListAttribute;
|
||||||
|
@ -62,6 +57,12 @@ public interface JpaFrom<O,T> extends JpaPath<T>, JpaFetchParent<O,T>, From<O,T>
|
||||||
@Incubating
|
@Incubating
|
||||||
<X> JpaJoinedFrom<?, X> join(JpaCteCriteria<X> cte, SqmJoinType joinType);
|
<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
|
// Covariant overrides
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.hibernate.metamodel.model.domain.MapPersistentAttribute;
|
||||||
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
|
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
|
||||||
import org.hibernate.metamodel.model.domain.SetPersistentAttribute;
|
import org.hibernate.metamodel.model.domain.SetPersistentAttribute;
|
||||||
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
|
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
|
||||||
|
import org.hibernate.query.criteria.JpaCrossJoin;
|
||||||
import org.hibernate.query.criteria.JpaCteCriteria;
|
import org.hibernate.query.criteria.JpaCteCriteria;
|
||||||
import org.hibernate.query.criteria.JpaDerivedJoin;
|
import org.hibernate.query.criteria.JpaDerivedJoin;
|
||||||
import org.hibernate.query.criteria.JpaJoinedFrom;
|
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
|
@Override
|
||||||
public Set<Fetch<T, ?>> getFetches() {
|
public Set<Fetch<T, ?>> getFetches() {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
package org.hibernate.query.sqm.tree.from;
|
package org.hibernate.query.sqm.tree.from;
|
||||||
|
|
||||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||||
|
import org.hibernate.query.criteria.JpaCrossJoin;
|
||||||
import org.hibernate.spi.NavigablePath;
|
import org.hibernate.spi.NavigablePath;
|
||||||
import org.hibernate.query.PathException;
|
import org.hibernate.query.PathException;
|
||||||
import org.hibernate.query.hql.spi.SqmCreationProcessingState;
|
import org.hibernate.query.hql.spi.SqmCreationProcessingState;
|
||||||
|
@ -24,7 +25,7 @@ import static org.hibernate.query.sqm.spi.SqmCreationHelper.buildRootNavigablePa
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @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;
|
private final SqmRoot<?> sqmRoot;
|
||||||
|
|
||||||
public SqmCrossJoin(
|
public SqmCrossJoin(
|
||||||
|
|
|
@ -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() );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,9 @@ import org.hibernate.dialect.H2Dialect;
|
||||||
import org.hibernate.dialect.PostgreSQLDialect;
|
import org.hibernate.dialect.PostgreSQLDialect;
|
||||||
import org.hibernate.dialect.PostgresPlusDialect;
|
import org.hibernate.dialect.PostgresPlusDialect;
|
||||||
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
||||||
|
import org.hibernate.query.criteria.JpaCrossJoin;
|
||||||
import org.hibernate.query.criteria.JpaExpression;
|
import org.hibernate.query.criteria.JpaExpression;
|
||||||
|
import org.hibernate.query.criteria.JpaRoot;
|
||||||
import org.hibernate.query.criteria.JpaWindow;
|
import org.hibernate.query.criteria.JpaWindow;
|
||||||
import org.hibernate.query.sqm.NullPrecedence;
|
import org.hibernate.query.sqm.NullPrecedence;
|
||||||
import org.hibernate.query.sqm.SortOrder;
|
import org.hibernate.query.sqm.SortOrder;
|
||||||
|
@ -309,17 +311,16 @@ public class CriteriaOrderedSetAggregateTest {
|
||||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsHypotheticalSetFunctions.class)
|
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsHypotheticalSetFunctions.class)
|
||||||
@RequiresDialect(H2Dialect.class)
|
@RequiresDialect(H2Dialect.class)
|
||||||
public void testHypotheticalSetRankWithGroupByHavingOrderByLimit(SessionFactoryScope scope) {
|
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 -> {
|
scope.inTransaction( session -> {
|
||||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||||
CriteriaQuery<Tuple> cr = cb.createQuery( Tuple.class );
|
CriteriaQuery<Tuple> cr = cb.createQuery( Tuple.class );
|
||||||
Root<EntityOfBasics> root1 = cr.from( EntityOfBasics.class );
|
JpaRoot<EntityOfBasics> e1 = (JpaRoot<EntityOfBasics>) cr.from( EntityOfBasics.class );
|
||||||
Root<EntityOfBasics> root2 = 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 )
|
cr.multiselect( e2.get( "id" ), function )
|
||||||
.groupBy( root2.get( "id" ) ).having( cb.gt( root2.get( "id" ), cb.literal( 1 ) ) )
|
.groupBy( e2.get( "id" ) ).having( cb.gt( e2.get( "id" ), cb.literal( 1 ) ) )
|
||||||
.orderBy( cb.asc( cb.literal( 1 ) ), cb.asc( cb.literal( 2 ) ) );
|
.orderBy( cb.asc( cb.literal( 1 ) ), cb.asc( cb.literal( 2 ) ) );
|
||||||
|
|
||||||
List<Tuple> resultList = session.createQuery( cr ).setFirstResult( 1 ).getResultList();
|
List<Tuple> resultList = session.createQuery( cr ).setFirstResult( 1 ).getResultList();
|
||||||
|
|
Loading…
Reference in New Issue