HHH-15367 Lift embedded/id-class to-one selection limitation for from clause subqueries

This commit is contained in:
Christian Beikov 2022-06-27 08:39:19 +02:00 committed by Andrea Boriero
parent 7676af4023
commit 9c660f7e0a
32 changed files with 3664 additions and 168 deletions

View File

@ -32,7 +32,6 @@ import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.query.QueryProducer;
import org.hibernate.type.StandardBasicTypes;
@ -2037,7 +2036,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
}
@Test
@SkipForDialect(value = TiDBDialect.class, comment = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(DialectChecks.SupportsSubqueryInOnClause.class)
public void test_hql_collection_index_operator_example_3() {
doInJPA(this::entityManagerFactory, entityManager -> {
//tag::hql-collection-index-operator-example[]
@ -3081,8 +3080,10 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
}
@Test
@SkipForDialect(value = TiDBDialect.class, comment = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(DialectChecks.SupportsOrderByInCorrelatedSubquery.class)
@RequiresDialectFeature({
DialectChecks.SupportsSubqueryInOnClause.class,
DialectChecks.SupportsOrderByInCorrelatedSubquery.class
})
public void test_hql_derived_join_example() {
doInJPA(this::entityManagerFactory, entityManager -> {

View File

@ -233,7 +233,7 @@ public abstract class AbstractCompositeIdentifierMapping
final TableReference defaultTableReference = tableGroup.resolveTableReference( navigablePath, getContainingTableExpression() );
getEmbeddableTypeDescriptor().forEachSelectable(
(columnIndex, selection) -> {
final TableReference tableReference = defaultTableReference.resolveTableReference( selection.getContainingTableExpression() ) != null
final TableReference tableReference = getContainingTableExpression().equals( selection.getContainingTableExpression() )
? defaultTableReference
: tableGroup.resolveTableReference( navigablePath, selection.getContainingTableExpression() );
final Expression columnReference = sqlAstCreationState.getSqlExpressionResolver()

View File

@ -256,7 +256,7 @@ public class EmbeddedAttributeMapping
final TableReference defaultTableReference = tableGroup.resolveTableReference( navigablePath, getContainingTableExpression() );
getEmbeddableTypeDescriptor().forEachSelectable(
(columnIndex, selection) -> {
final TableReference tableReference = defaultTableReference.resolveTableReference( selection.getContainingTableExpression() ) != null
final TableReference tableReference = getContainingTableExpression().equals( selection.getContainingTableExpression() )
? defaultTableReference
: tableGroup.resolveTableReference( navigablePath, selection.getContainingTableExpression() );
final Expression columnReference = sqlAstCreationState.getSqlExpressionResolver().resolveSqlExpression(

View File

@ -599,7 +599,7 @@ public class ToOneAttributeMapping
return null;
}
static void addPrefixedPropertyNames(
public static void addPrefixedPropertyNames(
Set<String> targetKeyPropertyNames,
String prefix,
Type type,

View File

@ -29,6 +29,7 @@ import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.FetchOptions;
@ -186,10 +187,14 @@ public class AnonymousTupleBasicValuedModelPart implements ModelPart, MappingTyp
FetchParent fetchParent,
SqlAstCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlExpressionResolver();
final TableReference tableReference = tableGroup.resolveTableReference(
navigablePath,
getContainingTableExpression()
);
final Expression expression = expressionResolver.resolveSqlExpression(
createColumnReferenceKey( tableGroup.getPrimaryTableReference(), getSelectionExpression() ),
createColumnReferenceKey( tableReference, getSelectionExpression() ),
sqlAstProcessingState -> new ColumnReference(
tableGroup.resolveTableReference( navigablePath, "" ),
tableReference,
this,
creationState.getCreationContext().getSessionFactory()
)

View File

@ -0,0 +1,103 @@
/*
* 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.derived;
import java.util.Map;
import org.hibernate.Incubating;
import org.hibernate.engine.spi.IdentifierValue;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.query.sqm.SqmExpressible;
/**
* @author Christian Beikov
*/
@Incubating
public class AnonymousTupleEmbeddedEntityIdentifierMapping extends AnonymousTupleEmbeddableValuedModelPart
implements CompositeIdentifierMapping, SingleAttributeIdentifierMapping {
private final CompositeIdentifierMapping delegate;
public AnonymousTupleEmbeddedEntityIdentifierMapping(
Map<String, ModelPart> modelParts,
DomainType<?> domainType,
String componentName,
CompositeIdentifierMapping delegate) {
super(
modelParts,
domainType,
componentName,
(EmbeddableValuedModelPart) delegate
);
this.delegate = delegate;
}
@Override
public IdentifierValue getUnsavedStrategy() {
return delegate.getUnsavedStrategy();
}
@Override
public Object getIdentifier(Object entity, SharedSessionContractImplementor session) {
return delegate.getIdentifier( entity, session );
}
@Override
public Object getIdentifier(Object entity) {
return delegate.getIdentifier( entity );
}
@Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
delegate.setIdentifier( entity, id, session );
}
@Override
public Object instantiate() {
return delegate.instantiate();
}
@Override
public PropertyAccess getPropertyAccess() {
return ((SingleAttributeIdentifierMapping) delegate).getPropertyAccess();
}
@Override
public String getAttributeName() {
return getPartName();
}
@Override
public boolean hasContainingClass() {
return true;
}
@Override
public EmbeddableMappingType getMappedIdEmbeddableTypeDescriptor() {
return this;
}
@Override
public MappingType getMappedType() {
return this;
}
@Override
public EmbeddableMappingType getPartMappingType() {
return (EmbeddableMappingType) super.getPartMappingType();
}
}

View File

@ -8,7 +8,10 @@ package org.hibernate.query.derived;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@ -20,12 +23,14 @@ import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
@ -42,11 +47,14 @@ import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.StandardTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
@ -70,6 +78,7 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
private final DomainType<?> domainType;
private final String componentName;
private final EntityValuedModelPart delegate;
private final Set<String> targetKeyPropertyNames;
public AnonymousTupleEntityValuedModelPart(
EntityIdentifierMapping identifierMapping,
@ -80,6 +89,17 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
this.domainType = domainType;
this.componentName = componentName;
this.delegate = delegate;
final EntityPersister persister = ((EntityMappingType) delegate.getPartMappingType())
.getEntityPersister();
final Set<String> targetKeyPropertyNames = new HashSet<>();
targetKeyPropertyNames.add( EntityIdentifierMapping.ROLE_LOCAL_NAME );
ToOneAttributeMapping.addPrefixedPropertyNames(
targetKeyPropertyNames,
persister.getIdentifierPropertyName(),
persister.getIdentifierType(),
persister.getFactory()
);
this.targetKeyPropertyNames = targetKeyPropertyNames;
}
@Override
@ -206,41 +226,72 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
SqlAstCreationContext creationContext) {
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
// Need to create a root table group and join predicate separately instead of a table group join directly,
// because the column names on the "key-side" have different names
final TableGroup tableGroup = ( (TableGroupJoinProducer) delegate ).createRootTableGroupJoin(
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
final boolean canUseInnerJoin = joinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins();
final EntityPersister entityPersister = delegate.getEntityMappingType().getEntityPersister();
final LazyTableGroup lazyTableGroup = new LazyTableGroup(
canUseInnerJoin,
navigablePath,
lhs,
explicitSourceAlias,
joinType,
fetched,
null,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
() -> createTableGroupInternal(
canUseInnerJoin,
navigablePath,
fetched,
null,
sqlAliasBase,
sqlExpressionResolver,
creationContext
),
(np, tableExpression) -> {
if ( !tableExpression.isEmpty() && !entityPersister.containsTableReference( tableExpression ) ) {
return false;
}
if ( navigablePath.equals( np.getParent() ) ) {
return targetKeyPropertyNames.contains( np.getLocalName() );
}
final String relativePath = np.relativize( navigablePath );
if ( relativePath == null ) {
return false;
}
// Empty relative path means the navigable paths are equal,
// in which case we allow resolving the parent table group
return relativePath.isEmpty() || targetKeyPropertyNames.contains( relativePath );
},
this,
explicitSourceAlias,
sqlAliasBase,
creationContext.getSessionFactory(),
lhs
);
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
tableGroup.getNavigablePath(),
lazyTableGroup.getNavigablePath(),
joinType,
tableGroup,
lazyTableGroup,
null
);
// -----------------
// Collect the selectable mappings for the FK key side and target side
// As we will "resolve" the derived column references for these mappings
// --------------
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) delegate;
final List<SelectableMapping> keyMappings;
final List<SelectableMapping> targetMappings;
if ( delegate instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) this.delegate;
final ModelPart targetJoinModelPart = toOneAttributeMapping.getForeignKeyDescriptor()
.getPart( toOneAttributeMapping.getSideNature().inverse() );
if ( associationMapping.isReferenceToPrimaryKey() && associationMapping.getSideNature() == ForeignKeyDescriptor.Nature.KEY ) {
final ModelPart targetJoinModelPart = associationMapping.getForeignKeyDescriptor()
.getPart( associationMapping.getSideNature().inverse() );
targetMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() );
targetJoinModelPart.forEachSelectable(
0,
(i, selectableMapping) -> targetMappings.add( selectableMapping )
);
keyMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() );
toOneAttributeMapping.getForeignKeyDescriptor()
.getPart( toOneAttributeMapping.getSideNature() )
associationMapping.getForeignKeyDescriptor()
.getPart( associationMapping.getSideNature() )
.forEachSelectable(
0,
(i, selectableMapping) -> keyMappings.add( selectableMapping )
@ -256,44 +307,113 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
keyMappings = targetMappings;
}
final TableReference tableReference = lhs.getPrimaryTableReference();
final List<ColumnReference> keyColumnReferences = new ArrayList<>( this.identifierMapping.getJdbcTypeCount() );
this.identifierMapping.forEachSelectable(
(i, selectableMapping) -> {
final SelectableMapping targetMapping = targetMappings.get( i );
final TableReference targetTableReference = tableGroup.resolveTableReference(
null,
targetMapping.getContainingTableExpression(),
false
);
tableGroupJoin.applyPredicate(
new ComparisonPredicate(
// It is important to resolve the sql expression here,
// as this selectableMapping is the "derived" one.
// We want to register the expression under the key of the original mapping
// which leads to this expression being used for a possible domain result
sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableReference,
keyMappings.get( i ).getSelectionExpression()
),
state -> new ColumnReference(
tableReference,
selectableMapping,
sessionFactory
)
// It is important to resolve the sql expression here,
// as this selectableMapping is the "derived" one.
// We want to register the expression under the key of the original mapping
// which leads to this expression being used for a possible domain result
keyColumnReferences.add(
(ColumnReference) sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableReference,
keyMappings.get( i ).getSelectionExpression()
),
ComparisonOperator.EQUAL,
new ColumnReference(
targetTableReference,
targetMapping,
state -> new ColumnReference(
tableReference,
selectableMapping,
sessionFactory
)
)
);
}
);
if ( keyMappings != targetMappings ) {
this.identifierMapping.forEachSelectable(
(i, selectableMapping) -> {
// It is important to resolve the sql expression here,
// as this selectableMapping is the "derived" one.
// We want to register the expression under the key of the original mapping
// which leads to this expression being used for a possible domain result
sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableReference,
targetMappings.get( i ).getSelectionExpression()
),
state -> new ColumnReference(
tableReference,
selectableMapping,
sessionFactory
)
);
}
);
}
lazyTableGroup.setTableGroupInitializerCallback(
tg -> {
this.identifierMapping.forEachSelectable(
(i, selectableMapping) -> {
final SelectableMapping targetMapping = targetMappings.get( i );
final TableReference targetTableReference = tg.resolveTableReference(
null,
targetMapping.getContainingTableExpression(),
false
);
tableGroupJoin.applyPredicate(
new ComparisonPredicate(
keyColumnReferences.get( i ),
ComparisonOperator.EQUAL,
new ColumnReference(
targetTableReference,
targetMapping,
sessionFactory
)
)
);
}
);
}
);
return tableGroupJoin;
}
public TableGroup createTableGroupInternal(
boolean canUseInnerJoins,
NavigablePath navigablePath,
boolean fetched,
String sourceAlias,
final SqlAliasBase sqlAliasBase,
SqlExpressionResolver sqlExpressionResolver,
SqlAstCreationContext creationContext) {
final EntityMappingType entityMappingType = delegate.getEntityMappingType();
final TableReference primaryTableReference = entityMappingType.createPrimaryTableReference(
sqlAliasBase,
sqlExpressionResolver,
creationContext
);
return new StandardTableGroup(
canUseInnerJoins,
navigablePath,
this,
fetched,
sourceAlias,
primaryTableReference,
true,
sqlAliasBase,
entityMappingType::containsTableReference,
(tableExpression, tg) -> entityMappingType.createTableReferenceJoin(
tableExpression,
sqlAliasBase,
primaryTableReference,
sqlExpressionResolver,
creationContext
),
creationContext.getSessionFactory()
);
}
@Override
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
@ -436,7 +556,7 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
@Override
public EntityIdentifierMapping getIdentifierMapping() {
return identifierMapping;
return delegate.getEntityMappingType().getIdentifierMapping();
}
@Override

View File

@ -0,0 +1,120 @@
/*
* 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.derived;
import java.util.Map;
import org.hibernate.Incubating;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.IdentifierValue;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.IdClassEmbeddable;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.VirtualIdEmbeddable;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.property.access.spi.PropertyAccess;
/**
* @author Christian Beikov
*/
@Incubating
public class AnonymousTupleNonAggregatedEntityIdentifierMapping extends AnonymousTupleEmbeddableValuedModelPart
implements NonAggregatedIdentifierMapping {
private final NonAggregatedIdentifierMapping delegate;
public AnonymousTupleNonAggregatedEntityIdentifierMapping(
Map<String, ModelPart> modelParts,
DomainType<?> domainType,
String componentName,
NonAggregatedIdentifierMapping delegate) {
super(
modelParts,
domainType,
componentName,
delegate
);
this.delegate = delegate;
}
@Override
public IdentifierValue getUnsavedStrategy() {
return delegate.getUnsavedStrategy();
}
@Override
public Object getIdentifier(Object entity, SharedSessionContractImplementor session) {
return delegate.getIdentifier( entity, session );
}
@Override
public Object getIdentifier(Object entity) {
return delegate.getIdentifier( entity );
}
@Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
delegate.setIdentifier( entity, id, session );
}
@Override
public Object instantiate() {
return delegate.instantiate();
}
@Override
public boolean hasContainingClass() {
return true;
}
@Override
public EmbeddableMappingType getMappedIdEmbeddableTypeDescriptor() {
return this;
}
@Override
public MappingType getMappedType() {
return this;
}
@Override
public EmbeddableMappingType getPartMappingType() {
return (EmbeddableMappingType) super.getPartMappingType();
}
@Override
public VirtualIdEmbeddable getVirtualIdEmbeddable() {
return delegate.getVirtualIdEmbeddable();
}
@Override
public IdClassEmbeddable getIdClassEmbeddable() {
return delegate.getIdClassEmbeddable();
}
@Override
public IdentifierValueMapper getIdentifierValueMapper() {
return delegate.getIdentifierValueMapper();
}
@Override
public FetchStyle getStyle() {
return FetchStyle.JOIN;
}
@Override
public FetchTiming getTiming() {
return FetchTiming.IMMEDIATE;
}
}

View File

@ -25,17 +25,17 @@ import org.hibernate.type.descriptor.java.JavaType;
* @author Christian Beikov
*/
@Incubating
public class AnonymousTuplePersistentSingularAttribute<O, J> extends AnonymousTupleSqmPathSource<J> implements
public class AnonymousTupleSqmAssociationPathSource<O, J> extends AnonymousTupleSqmPathSource<J> implements
SingularPersistentAttribute<O, J> {
private final SingularPersistentAttribute<O, J> delegate;
private final SimpleDomainType<J> domainType;
public AnonymousTuplePersistentSingularAttribute(
public AnonymousTupleSqmAssociationPathSource(
String localPathName,
SqmPath<J> path,
SingularPersistentAttribute<O, J> delegate) {
SimpleDomainType<J> domainType) {
super( localPathName, path );
this.delegate = delegate;
this.domainType = domainType;
}
@Override
@ -57,66 +57,66 @@ public class AnonymousTuplePersistentSingularAttribute<O, J> extends AnonymousTu
@Override
public SimpleDomainType<J> getType() {
return delegate.getType();
return domainType;
}
@Override
public ManagedDomainType<O> getDeclaringType() {
return delegate.getDeclaringType();
return null;
}
@Override
public boolean isId() {
return delegate.isId();
return false;
}
@Override
public boolean isVersion() {
return delegate.isVersion();
return false;
}
@Override
public boolean isOptional() {
return delegate.isOptional();
return true;
}
@Override
public JavaType<J> getAttributeJavaType() {
return delegate.getAttributeJavaType();
return domainType.getExpressibleJavaType();
}
@Override
public AttributeClassification getAttributeClassification() {
return delegate.getAttributeClassification();
return AttributeClassification.MANY_TO_ONE;
}
@Override
public SimpleDomainType<?> getKeyGraphType() {
return delegate.getKeyGraphType();
return domainType;
}
@Override
public String getName() {
return delegate.getName();
return getPathName();
}
@Override
public PersistentAttributeType getPersistentAttributeType() {
return delegate.getPersistentAttributeType();
return PersistentAttributeType.MANY_TO_ONE;
}
@Override
public Member getJavaMember() {
return delegate.getJavaMember();
return null;
}
@Override
public boolean isAssociation() {
return delegate.isAssociation();
return true;
}
@Override
public boolean isCollection() {
return delegate.isCollection();
return false;
}
}

View File

@ -18,6 +18,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -26,6 +27,8 @@ import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.model.domain.DomainType;
@ -41,6 +44,7 @@ import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.PluralTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupProducer;
import org.hibernate.sql.results.graph.DomainResult;
@ -116,6 +120,9 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
}
private ModelPart getModelPart(TableGroup tableGroup) {
if ( tableGroup instanceof PluralTableGroup ) {
tableGroup = ( (PluralTableGroup) tableGroup ).getElementTableGroup();
}
if ( tableGroup instanceof LazyTableGroup && ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup() != null ) {
return ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup().getModelPart();
}
@ -137,13 +144,39 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
.getIdentifierMapping();
final EntityIdentifierMapping newIdentifierMapping;
if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) {
final String attributeName = ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName();
if ( identifierMapping.getPartMappingType() instanceof ManagedMappingType ) {
// todo: implement
throw new UnsupportedOperationException("Support for embedded id in anonymous tuples is not yet implemented");
//noinspection unchecked
final Set<Attribute<?, ?>> attributes = (Set<Attribute<?, ?>>) ( (ManagedDomainType<?>) ( (EntityDomainType<?>) domainType ).getIdentifierDescriptor().getSqmPathType() ).getAttributes();
final Map<String, ModelPart> modelParts = CollectionHelper.linkedMapOfSize( attributes.size() );
final EmbeddableValuedModelPart modelPartContainer = (EmbeddableValuedModelPart) identifierMapping;
for ( Attribute<?, ?> attribute : attributes ) {
if ( !( attribute instanceof SingularPersistentAttribute<?, ?> ) ) {
throw new IllegalArgumentException( "Only embeddables without collections are supported!" );
}
final DomainType<?> attributeType = ( (SingularPersistentAttribute<?, ?>) attribute ).getType();
final ModelPart modelPart = createModelPart(
sqmExpressible,
attributeType,
sqlSelections,
selectionIndex,
selectionExpression + "_" + attributeName + "_" + attribute.getName(),
attribute.getName(),
modelPartContainer.findSubPart( attribute.getName(), null ),
compatibleTableExpressions
);
modelParts.put( modelPart.getPartName(), modelPart );
}
newIdentifierMapping = new AnonymousTupleEmbeddedEntityIdentifierMapping(
modelParts,
domainType,
attributeName,
(CompositeIdentifierMapping) identifierMapping
);
}
else {
newIdentifierMapping = new AnonymousTupleBasicEntityIdentifierMapping(
selectionExpression + "_" + ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName(),
selectionExpression + "_" + attributeName,
sqmExpressible,
sqlSelections.get( selectionIndex )
.getExpressionType()
@ -154,8 +187,33 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
}
}
else {
// todo: implement
throw new UnsupportedOperationException("Support for id-class in anonymous tuples is not yet implemented");
//noinspection unchecked
final Set<Attribute<?, ?>> attributes = (Set<Attribute<?, ?>>) ( (ManagedDomainType<?>) ( (EntityDomainType<?>) domainType ).getIdentifierDescriptor().getSqmPathType() ).getAttributes();
final Map<String, ModelPart> modelParts = CollectionHelper.linkedMapOfSize( attributes.size() );
final EmbeddableValuedModelPart modelPartContainer = (EmbeddableValuedModelPart) identifierMapping;
for ( Attribute<?, ?> attribute : attributes ) {
if ( !( attribute instanceof SingularPersistentAttribute<?, ?> ) ) {
throw new IllegalArgumentException( "Only embeddables without collections are supported!" );
}
final DomainType<?> attributeType = ( (SingularPersistentAttribute<?, ?>) attribute ).getType();
final ModelPart modelPart = createModelPart(
sqmExpressible,
attributeType,
sqlSelections,
selectionIndex,
selectionExpression + "_" + attribute.getName(),
attribute.getName(),
modelPartContainer.findSubPart( attribute.getName(), null ),
compatibleTableExpressions
);
modelParts.put( modelPart.getPartName(), modelPart );
}
newIdentifierMapping = new AnonymousTupleNonAggregatedEntityIdentifierMapping(
modelParts,
domainType,
selectionExpression,
(NonAggregatedIdentifierMapping) identifierMapping
);
}
if ( existingModelPartContainer instanceof ToOneAttributeMapping ) {
// We take "ownership" of FK columns by reporting the derived table group is compatible

View File

@ -15,6 +15,9 @@ import org.hibernate.Incubating;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.UnsupportedMappingException;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.metamodel.model.domain.SimpleDomainType;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.metamodel.model.domain.TupleType;
import org.hibernate.query.ReturnableType;
@ -126,7 +129,27 @@ public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, Retur
final SqmPath<?> sqmPath = (SqmPath<?>) component;
if ( sqmPath.getNodeType() instanceof SingularPersistentAttribute<?, ?> ) {
//noinspection unchecked,rawtypes
return new AnonymousTuplePersistentSingularAttribute( name, sqmPath, (SingularPersistentAttribute<?, ?>) sqmPath.getNodeType() );
return new AnonymousTupleSqmAssociationPathSource(
name,
sqmPath,
( (SingularPersistentAttribute<?, ?>) sqmPath.getNodeType() ).getType()
);
}
else if ( sqmPath.getNodeType() instanceof PluralPersistentAttribute<?, ?, ?> ) {
//noinspection unchecked,rawtypes
return new AnonymousTupleSqmAssociationPathSource(
name,
sqmPath,
( (PluralPersistentAttribute<?, ?, ?>) sqmPath.getNodeType() ).getElementType()
);
}
else if ( sqmPath.getNodeType() instanceof EntityDomainType<?> ) {
//noinspection unchecked,rawtypes
return new AnonymousTupleSqmAssociationPathSource(
name,
sqmPath,
(SimpleDomainType<?>) sqmPath.getNodeType()
);
}
else {
return new AnonymousTupleSqmPathSource<>( name, sqmPath );

View File

@ -799,8 +799,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private static void verifyManipulationImplicitJoin(TableGroup tableGroup) {
//noinspection StatementWithEmptyBody
if ( tableGroup instanceof LazyTableGroup && ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup() == null
|| tableGroup instanceof VirtualTableGroup ) {
if ( !tableGroup.isInitialized() || tableGroup instanceof VirtualTableGroup ) {
// this is fine
}
else {
@ -1119,7 +1118,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private static boolean hasJoins(List<TableGroupJoin> tableGroupJoins) {
for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) {
final TableGroup joinedGroup = tableGroupJoin.getJoinedGroup();
if ( joinedGroup instanceof LazyTableGroup && ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup() == null ) {
if ( !joinedGroup.isInitialized() ) {
continue;
}
return true;
@ -3061,7 +3060,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else {
idPath = componentName + "_" + idAttribute.getName();
}
addColumnNames( columnNames, entityDomainType.getIdType(), idPath );
addColumnNames( columnNames, entityDomainType.getIdentifierDescriptor().getSqmPathType(), idPath );
}
else if ( domainType instanceof ManagedDomainType<?> ) {
for ( Attribute<?, ?> attribute : ( (ManagedDomainType<?>) domainType ).getAttributes() ) {

View File

@ -139,11 +139,7 @@ public class SqlTreePrinter {
}
private void logTableGroupDetails(TableGroup tableGroup) {
if ( tableGroup instanceof LazyTableGroup ) {
TableGroup underlyingTableGroup = ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup();
if ( underlyingTableGroup != null ) {
logTableGroupDetails( underlyingTableGroup );
}
if ( !tableGroup.isInitialized() ) {
return;
}
if ( tableGroup.getPrimaryTableReference() instanceof NamedTableReference ) {

View File

@ -3135,6 +3135,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
else {
int offset = 0;
for ( int i = 0; i < size; i++ ) {
final SqlSelection sqlSelection = sqlSelections.get( i );
appendSql( separator );
@ -3144,10 +3145,10 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
else {
parameterRenderingMode = defaultRenderingMode;
}
visitSqlSelection( sqlSelection );
offset += visitSqlSelectExpression( sqlSelection.getExpression(), offset, columnAliases );
parameterRenderingMode = original;
appendSql( WHITESPACE );
appendSql( columnAliases.get( i ) );
appendSql( columnAliases.get( offset - 1 ) );
separator = COMA_SEPARATOR;
}
}
@ -3173,6 +3174,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
else {
String separator = NO_SEPARATOR;
int offset = 0;
for ( int i = 0; i < size; i++ ) {
final SqlSelection sqlSelection = sqlSelections.get( i );
appendSql( separator );
@ -3182,9 +3184,9 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
else {
parameterRenderingMode = defaultRenderingMode;
}
visitSqlSelection( sqlSelection );
offset += visitSqlSelectExpression( sqlSelection.getExpression(), offset, columnAliases );
appendSql( WHITESPACE );
appendSql( columnAliases.get( i ) );
appendSql( columnAliases.get( offset - 1 ) );
parameterRenderingMode = original;
separator = COMA_SEPARATOR;
}
@ -3537,6 +3539,32 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
protected int visitSqlSelectExpression(Expression expression, int offset, List<String> columnAliases) {
final SqlTuple sqlTuple = SqlTupleContainer.getSqlTuple( expression );
if ( sqlTuple != null ) {
boolean isFirst = true;
final List<? extends Expression> expressions = sqlTuple.getExpressions();
int i = 0;
for ( ; i < expressions.size(); i++ ) {
Expression e = expressions.get( i );
if ( isFirst ) {
isFirst = false;
}
else {
appendSql( WHITESPACE );
appendSql( columnAliases.get( offset + i - 1 ) );
appendSql( ',' );
}
renderSelectExpression( e );
}
return i;
}
else {
renderSelectExpression( expression );
return 1;
}
}
protected void renderSelectExpression(Expression expression) {
renderExpressionAsClauseItem( expression );
}
@ -3805,19 +3833,15 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
protected boolean hasNestedTableGroupsToRender(List<TableGroupJoin> nestedTableGroupJoins) {
for ( TableGroupJoin nestedTableGroupJoin : nestedTableGroupJoins ) {
final TableGroup joinedGroup = nestedTableGroupJoin.getJoinedGroup();
final TableGroup realTableGroup;
if ( joinedGroup instanceof LazyTableGroup ) {
realTableGroup = ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup();
if ( !joinedGroup.isInitialized() ) {
continue;
}
else {
realTableGroup = joinedGroup;
}
if ( realTableGroup instanceof VirtualTableGroup ) {
if ( hasNestedTableGroupsToRender( realTableGroup.getNestedTableGroupJoins() ) ) {
if ( joinedGroup instanceof VirtualTableGroup ) {
if ( hasNestedTableGroupsToRender( joinedGroup.getNestedTableGroupJoins() ) ) {
return true;
}
}
else if ( realTableGroup != null ) {
else {
return true;
}
}
@ -3998,24 +4022,17 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
protected void processTableGroupJoin(TableGroupJoin tableGroupJoin, List<TableGroupJoin> tableGroupJoinCollector) {
final TableGroup joinedGroup = tableGroupJoin.getJoinedGroup();
final TableGroup realTableGroup;
if ( joinedGroup instanceof LazyTableGroup ) {
realTableGroup = ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup();
}
else {
realTableGroup = joinedGroup;
}
if ( realTableGroup instanceof VirtualTableGroup ) {
processNestedTableGroupJoins( realTableGroup, tableGroupJoinCollector );
if ( joinedGroup instanceof VirtualTableGroup ) {
processNestedTableGroupJoins( joinedGroup, tableGroupJoinCollector );
if ( tableGroupJoinCollector != null ) {
tableGroupJoinCollector.addAll( realTableGroup.getTableGroupJoins() );
tableGroupJoinCollector.addAll( joinedGroup.getTableGroupJoins() );
}
else {
processTableGroupJoins( realTableGroup );
processTableGroupJoins( joinedGroup );
}
}
else if ( realTableGroup != null ) {
else if ( joinedGroup.isInitialized() ) {
renderTableGroupJoin(
tableGroupJoin,
tableGroupJoinCollector

View File

@ -200,4 +200,9 @@ public abstract class DelegatingTableGroup implements TableGroup {
public boolean isFetched() {
return getTableGroup().isFetched();
}
@Override
public boolean isInitialized() {
return getTableGroup().isInitialized();
}
}

View File

@ -65,6 +65,11 @@ public class LazyTableGroup extends DelegatingTableGroup {
this.parentTableGroup = parentTableGroup;
}
@Override
public boolean isInitialized() {
return tableGroup != null;
}
public TableGroup getUnderlyingTableGroup() {
return tableGroup;
}

View File

@ -151,4 +151,12 @@ public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, SqmPat
default boolean isFetched() {
return false;
}
/**
* If this is a lazy table group, it may report that it is not initialized,
* which would also mean that a join referring to this table group should not be rendered.
*/
default boolean isInitialized() {
return true;
}
}

View File

@ -11,8 +11,8 @@ import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.AfterAll;
@ -248,7 +248,7 @@ public class OuterJoinTest {
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
public void testJoinOrderWithRightJoinWithInnerImplicitJoins(EntityManagerFactoryScope scope) {
scope.inTransaction( em -> {
List<Tuple> resultList = em.createQuery(
@ -378,7 +378,7 @@ public class OuterJoinTest {
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
public void testSubQueryInOnClause(EntityManagerFactoryScope scope) {
scope.inTransaction(
em -> {
@ -395,7 +395,7 @@ public class OuterJoinTest {
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
public void testSubQueryCorrelationInOnClause(EntityManagerFactoryScope scope) {
scope.inTransaction(
em -> {

View File

@ -6,10 +6,8 @@
*/
package org.hibernate.orm.test.query;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
@ -19,13 +17,12 @@ import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -43,18 +40,19 @@ import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromEmbeddedIdTests.Contact.class)
@SessionFactory
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@TestForIssue( jiraKey = "HHH-")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromEmbeddedIdTests {
@Test
@FailureExpected(reason = "Support for embedded id association selecting in from clause subqueries not yet supported")
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
@ -72,27 +70,32 @@ public class SubQueryInFromEmbeddedIdTests {
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) );
cq.where( root.get( "alternativeContact" ).isNotNull() );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by address.name.first" +
"order by alt.name.first " +
"limit 1" +
") a " +
"where c.alternativeContact is not null",
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId1() );
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId2() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Contact.ContactId.class ).getId1() );
assertEquals( 3, list.get( 1 ).get( 1, Contact.ContactId.class ).getId2() );
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Contact.ContactId.class ) );
}
);
}
@ -100,7 +103,6 @@ public class SubQueryInFromEmbeddedIdTests {
}
@Test
@FailureExpected(reason = "Support for embedded id association selecting in from clause subqueries not yet supported")
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
@ -119,25 +121,29 @@ public class SubQueryInFromEmbeddedIdTests {
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first desc" +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt",
"join a.contact alt " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
@ -145,7 +151,6 @@ public class SubQueryInFromEmbeddedIdTests {
}
@Test
@FailureExpected(reason = "Support for embedded id association selecting in from clause subqueries not yet supported")
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
@ -163,24 +168,28 @@ public class SubQueryInFromEmbeddedIdTests {
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first desc" +
"order by alt.name.first desc " +
"limit 1" +
") a",
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
@ -213,6 +222,7 @@ public class SubQueryInFromEmbeddedIdTests {
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "update Contact set alternativeContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).executeUpdate();
} );
}

View File

@ -8,7 +8,6 @@ package org.hibernate.orm.test.query;
import java.util.function.Consumer;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
@ -20,11 +19,9 @@ import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -42,18 +39,18 @@ import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromIdClassTests.Contact.class)
@SessionFactory
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromIdClassTests {
@Test
@FailureExpected(reason = "Support for id class association selecting in from clause subqueries not yet supported")
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
@ -70,28 +67,34 @@ public class SubQueryInFromIdClassTests {
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) );
cq.where( root.get( "alternativeContact" ).isNotNull() );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id1" ), a.get( "contact" ).get( "id2" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id1, a.contact.id2 from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by address.name.first" +
"order by alt.name.first " +
"limit 1" +
") a " +
"where c.alternativeContact is not null",
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
assertEquals( 2, list.get( 0 ).get( 2, Integer.class ) );
assertEquals( "Jane", list.get( 1 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Integer.class ) );
assertEquals( 3, list.get( 1 ).get( 2, Integer.class ) );
assertEquals( "Granny", list.get( 2 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Integer.class ) );
assertNull( list.get( 2 ).get( 2, Integer.class ) );
}
);
}
@ -99,7 +102,6 @@ public class SubQueryInFromIdClassTests {
}
@Test
@FailureExpected(reason = "Support for id class association selecting in from clause subqueries not yet supported")
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
@ -118,25 +120,29 @@ public class SubQueryInFromIdClassTests {
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first desc" +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt",
"join a.contact alt " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
}
);
}
@ -144,7 +150,6 @@ public class SubQueryInFromIdClassTests {
}
@Test
@FailureExpected(reason = "Support for id class association selecting in from clause subqueries not yet supported")
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
@ -162,24 +167,28 @@ public class SubQueryInFromIdClassTests {
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first desc" +
"order by alt.name.first desc " +
"limit 1" +
") a",
") a " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
}
);
}
@ -212,6 +221,7 @@ public class SubQueryInFromIdClassTests {
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "update Contact set alternativeContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).executeUpdate();
} );
}

View File

@ -0,0 +1,353 @@
/*
* 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;
import java.util.function.Consumer;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
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.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromInverseOneEmbeddedIdTests.Contact.class)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromInverseOneEmbeddedIdTests {
@Test
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContact = correlatedRoot.join( "alternativeContact" );
subquery.multiselect( alternativeContact.alias( "contact" ) );
subquery.orderBy( cb.asc( alternativeContact.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId1() );
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId2() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Contact.ContactId.class ).getId1() );
assertEquals( 3, list.get( 1 ).get( 1, Contact.ContactId.class ).getId2() );
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Contact.ContactId.class ) );
}
);
}
);
}
@Test
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContact = correlatedRoot.join( "alternativeContact" );
subquery.multiselect( alternativeContact.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContact.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContact = correlatedRoot.join( "alternativeContact" );
subquery.multiselect( alternativeContact.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContact.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Contact contact = new Contact(
1,
new Contact.Name( "John", "Doe" )
);
final Contact alternativeContact = new Contact(
2,
new Contact.Name( "Jane", "Doe" )
);
final Contact alternativeContact2 = new Contact(
3,
new Contact.Name( "Granny", "Doe" )
);
alternativeContact2.setPrimaryContact( alternativeContact );
alternativeContact.setPrimaryContact( contact );
session.persist( contact );
session.persist( alternativeContact );
session.persist( alternativeContact2 );
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "update Contact set primaryContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
/**
* @author Steve Ebersole
*/
@Entity( name = "Contact")
@Table( name = "contacts" )
@SecondaryTable( name="contact_supp" )
public static class Contact {
private ContactId id;
private Name name;
private Contact alternativeContact;
private Contact primaryContact;
public Contact() {
}
public Contact(Integer id, Name name) {
this.id = new ContactId( id, id );
this.name = name;
}
@EmbeddedId
public ContactId getId() {
return id;
}
public void setId(ContactId id) {
this.id = id;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@OneToOne(fetch = FetchType.LAZY, mappedBy = "primaryContact")
public Contact getAlternativeContact() {
return alternativeContact;
}
public void setAlternativeContact(Contact alternativeContact) {
this.alternativeContact = alternativeContact;
}
@OneToOne(fetch = FetchType.LAZY)
public Contact getPrimaryContact() {
return primaryContact;
}
public void setPrimaryContact(Contact primaryContact) {
this.primaryContact = primaryContact;
}
@Embeddable
public static class ContactId {
private Integer id1;
private Integer id2;
public ContactId() {
}
public ContactId(Integer id1, Integer id2) {
this.id1 = id1;
this.id2 = id2;
}
public Integer getId1() {
return id1;
}
public void setId1(Integer id1) {
this.id1 = id1;
}
public Integer getId2() {
return id2;
}
public void setId2(Integer id2) {
this.id2 = id2;
}
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -0,0 +1,334 @@
/*
* 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;
import java.util.function.Consumer;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
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.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromInverseOneIdClassTests.Contact.class)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromInverseOneIdClassTests {
@Test
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContact = correlatedRoot.join( "alternativeContact" );
subquery.multiselect( alternativeContact.alias( "contact" ) );
subquery.orderBy( cb.asc( alternativeContact.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id1" ), a.get( "contact" ).get( "id2" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id1, a.contact.id2 from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first " +
"limit 1" +
") a " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
assertEquals( 2, list.get( 0 ).get( 2, Integer.class ) );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Integer.class ) );
assertEquals( 3, list.get( 1 ).get( 2, Integer.class ) );
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Integer.class ) );
assertNull( list.get( 2 ).get( 2, Integer.class ) );
}
);
}
);
}
@Test
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContact = correlatedRoot.join( "alternativeContact" );
subquery.multiselect( alternativeContact.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContact.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContact = correlatedRoot.join( "alternativeContact" );
subquery.multiselect( alternativeContact.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContact.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Contact contact = new Contact(
1,
new Contact.Name( "John", "Doe" )
);
final Contact alternativeContact = new Contact(
2,
new Contact.Name( "Jane", "Doe" )
);
final Contact alternativeContact2 = new Contact(
3,
new Contact.Name( "Granny", "Doe" )
);
alternativeContact2.setPrimaryContact( alternativeContact );
alternativeContact.setPrimaryContact( contact );
session.persist( contact );
session.persist( alternativeContact );
session.persist( alternativeContact2 );
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "update Contact set primaryContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
/**
* @author Steve Ebersole
*/
@Entity( name = "Contact")
@Table( name = "contacts" )
@SecondaryTable( name="contact_supp" )
public static class Contact {
private Integer id1;
private Integer id2;
private Name name;
private Contact alternativeContact;
private Contact primaryContact;
public Contact() {
}
public Contact(Integer id, Contact.Name name) {
this.id1 = id;
this.id2 = id;
this.name = name;
}
@Id
public Integer getId1() {
return id1;
}
public void setId1(Integer id1) {
this.id1 = id1;
}
@Id
public Integer getId2() {
return id2;
}
public void setId2(Integer id2) {
this.id2 = id2;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@OneToOne(fetch = FetchType.LAZY, mappedBy = "primaryContact")
public Contact getAlternativeContact() {
return alternativeContact;
}
public void setAlternativeContact(Contact alternativeContact) {
this.alternativeContact = alternativeContact;
}
@OneToOne(fetch = FetchType.LAZY)
public Contact getPrimaryContact() {
return primaryContact;
}
public void setPrimaryContact(Contact primaryContact) {
this.primaryContact = primaryContact;
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -0,0 +1,320 @@
/*
* 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;
import java.util.function.Consumer;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
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.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromInverseOneTests.Contact.class)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromInverseOneTests {
@Test
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContact = correlatedRoot.join( "alternativeContact" );
subquery.multiselect( alternativeContact.alias( "contact" ) );
subquery.orderBy( cb.asc( alternativeContact.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Integer.class ) );
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Integer.class ) );
}
);
}
);
}
@Test
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContact = correlatedRoot.join( "alternativeContact" );
subquery.multiselect( alternativeContact.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContact.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContact = correlatedRoot.join( "alternativeContact" );
subquery.multiselect( alternativeContact.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContact.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Contact contact = new Contact(
1,
new Contact.Name( "John", "Doe" )
);
final Contact alternativeContact = new Contact(
2,
new Contact.Name( "Jane", "Doe" )
);
final Contact alternativeContact2 = new Contact(
3,
new Contact.Name( "Granny", "Doe" )
);
alternativeContact2.setPrimaryContact( alternativeContact );
alternativeContact.setPrimaryContact( contact );
session.persist( contact );
session.persist( alternativeContact );
session.persist( alternativeContact2 );
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "update Contact set primaryContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
/**
* @author Steve Ebersole
*/
@Entity( name = "Contact")
@Table( name = "contacts" )
@SecondaryTable( name="contact_supp" )
public static class Contact {
private Integer id;
private Contact.Name name;
private Contact alternativeContact;
private Contact primaryContact;
public Contact() {
}
public Contact(Integer id, Contact.Name name) {
this.id = id;
this.name = name;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Contact.Name getName() {
return name;
}
public void setName(Contact.Name name) {
this.name = name;
}
@OneToOne(fetch = FetchType.LAZY, mappedBy = "primaryContact")
public Contact getAlternativeContact() {
return alternativeContact;
}
public void setAlternativeContact(Contact alternativeContact) {
this.alternativeContact = alternativeContact;
}
@OneToOne(fetch = FetchType.LAZY)
public Contact getPrimaryContact() {
return primaryContact;
}
public void setPrimaryContact(Contact primaryContact) {
this.primaryContact = primaryContact;
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -0,0 +1,344 @@
/*
* 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;
import java.util.Set;
import java.util.function.Consumer;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
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.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromManyToManyEmbeddedIdTests.Contact.class)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromManyToManyEmbeddedIdTests {
@Test
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.asc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId1() );
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId2() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Contact.ContactId.class ).getId1() );
assertEquals( 3, list.get( 1 ).get( 1, Contact.ContactId.class ).getId2() );
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Contact.ContactId.class ) );
}
);
}
);
}
@Test
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Contact contact = new Contact(
1,
new Contact.Name( "John", "Doe" )
);
final Contact alternativeContact = new Contact(
2,
new Contact.Name( "Jane", "Doe" )
);
final Contact alternativeContact2 = new Contact(
3,
new Contact.Name( "Granny", "Doe" )
);
contact.alternativeContacts = Set.of( alternativeContact );
alternativeContact.alternativeContacts = Set.of( alternativeContact2 );
session.persist( alternativeContact2 );
session.persist( alternativeContact );
session.persist( contact );
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
/**
* @author Steve Ebersole
*/
@Entity( name = "Contact")
@Table( name = "contacts" )
@SecondaryTable( name="contact_supp" )
public static class Contact {
private ContactId id;
private Name name;
private Set<Contact> alternativeContacts;
public Contact() {
}
public Contact(Integer id, Name name) {
this.id = new ContactId( id, id );
this.name = name;
}
@EmbeddedId
public ContactId getId() {
return id;
}
public void setId(ContactId id) {
this.id = id;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@ManyToMany
public Set<Contact> getAlternativeContacts() {
return alternativeContacts;
}
public void setAlternativeContacts(Set<Contact> alternativeContacts) {
this.alternativeContacts = alternativeContacts;
}
@Embeddable
public static class ContactId {
private Integer id1;
private Integer id2;
public ContactId() {
}
public ContactId(Integer id1, Integer id2) {
this.id1 = id1;
this.id2 = id2;
}
public Integer getId1() {
return id1;
}
public void setId1(Integer id1) {
this.id1 = id1;
}
public Integer getId2() {
return id2;
}
public void setId2(Integer id2) {
this.id2 = id2;
}
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -0,0 +1,326 @@
/*
* 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;
import java.util.Set;
import java.util.function.Consumer;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
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.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromManyToManyIdClassTests.Contact.class)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromManyToManyIdClassTests {
@Test
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.asc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id1" ), a.get( "contact" ).get( "id2" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id1, a.contact.id2 from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first " +
"limit 1" +
") a " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
assertEquals( 2, list.get( 0 ).get( 2, Integer.class ) );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Integer.class ) );
assertEquals( 3, list.get( 1 ).get( 2, Integer.class ) );
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Integer.class ) );
assertNull( list.get( 2 ).get( 2, Integer.class ) );
}
);
}
);
}
@Test
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Contact contact = new Contact(
1,
new Contact.Name( "John", "Doe" )
);
final Contact alternativeContact = new Contact(
2,
new Contact.Name( "Jane", "Doe" )
);
final Contact alternativeContact2 = new Contact(
3,
new Contact.Name( "Granny", "Doe" )
);
contact.alternativeContacts = Set.of( alternativeContact );
alternativeContact.alternativeContacts = Set.of( alternativeContact2 );
session.persist( alternativeContact2 );
session.persist( alternativeContact );
session.persist( contact );
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
/**
* @author Steve Ebersole
*/
@Entity( name = "Contact")
@Table( name = "contacts" )
@SecondaryTable( name="contact_supp" )
public static class Contact {
private Integer id1;
private Integer id2;
private Name name;
private Set<Contact> alternativeContacts;
public Contact() {
}
public Contact(Integer id, Name name) {
this.id1 = id;
this.id2 = id;
this.name = name;
}
@Id
public Integer getId1() {
return id1;
}
public void setId1(Integer id1) {
this.id1 = id1;
}
@Id
public Integer getId2() {
return id2;
}
public void setId2(Integer id2) {
this.id2 = id2;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@ManyToMany
public Set<Contact> getAlternativeContacts() {
return alternativeContacts;
}
public void setAlternativeContacts(Set<Contact> alternativeContacts) {
this.alternativeContacts = alternativeContacts;
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -0,0 +1,313 @@
/*
* 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;
import java.util.Set;
import java.util.function.Consumer;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
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.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromManyToManyTests.Contact.class)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromManyToManyTests {
@Test
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.asc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Integer.class ) );
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Integer.class ) );
}
);
}
);
}
@Test
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Contact contact = new Contact(
1,
new Contact.Name( "John", "Doe" )
);
final Contact alternativeContact = new Contact(
2,
new Contact.Name( "Jane", "Doe" )
);
final Contact alternativeContact2 = new Contact(
3,
new Contact.Name( "Granny", "Doe" )
);
contact.alternativeContacts = Set.of( alternativeContact );
alternativeContact.alternativeContacts = Set.of( alternativeContact2 );
session.persist( alternativeContact2 );
session.persist( alternativeContact );
session.persist( contact );
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
/**
* @author Steve Ebersole
*/
@Entity( name = "Contact")
@Table( name = "contacts" )
@SecondaryTable( name="contact_supp" )
public static class Contact {
private Integer id;
private Name name;
private Set<Contact> alternativeContacts;
private Contact primaryContact;
public Contact() {
}
public Contact(Integer id, Name name) {
this.id = id;
this.name = name;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@ManyToMany
public Set<Contact> getAlternativeContacts() {
return alternativeContacts;
}
public void setAlternativeContacts(Set<Contact> alternativeContacts) {
this.alternativeContacts = alternativeContacts;
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -0,0 +1,354 @@
/*
* 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;
import java.util.Set;
import java.util.function.Consumer;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
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.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromOneToManyEmbeddedIdTests.Contact.class)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromOneToManyEmbeddedIdTests {
@Test
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.asc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId1() );
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId2() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Contact.ContactId.class ).getId1() );
assertEquals( 3, list.get( 1 ).get( 1, Contact.ContactId.class ).getId2() );
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Contact.ContactId.class ) );
}
);
}
);
}
@Test
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Contact contact = new Contact(
1,
new Contact.Name( "John", "Doe" )
);
final Contact alternativeContact = new Contact(
2,
new Contact.Name( "Jane", "Doe" )
);
final Contact alternativeContact2 = new Contact(
3,
new Contact.Name( "Granny", "Doe" )
);
alternativeContact2.setPrimaryContact( alternativeContact );
alternativeContact.setPrimaryContact( contact );
session.persist( contact );
session.persist( alternativeContact );
session.persist( alternativeContact2 );
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "update Contact set primaryContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
/**
* @author Steve Ebersole
*/
@Entity( name = "Contact")
@Table( name = "contacts" )
@SecondaryTable( name="contact_supp" )
public static class Contact {
private ContactId id;
private Name name;
private Set<Contact> alternativeContacts;
private Contact primaryContact;
public Contact() {
}
public Contact(Integer id, Name name) {
this.id = new ContactId( id, id );
this.name = name;
}
@EmbeddedId
public ContactId getId() {
return id;
}
public void setId(ContactId id) {
this.id = id;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@OneToMany(mappedBy = "primaryContact")
public Set<Contact> getAlternativeContacts() {
return alternativeContacts;
}
public void setAlternativeContacts(Set<Contact> alternativeContacts) {
this.alternativeContacts = alternativeContacts;
}
@OneToOne(fetch = FetchType.LAZY)
public Contact getPrimaryContact() {
return primaryContact;
}
public void setPrimaryContact(Contact primaryContact) {
this.primaryContact = primaryContact;
}
@Embeddable
public static class ContactId {
private Integer id1;
private Integer id2;
public ContactId() {
}
public ContactId(Integer id1, Integer id2) {
this.id1 = id1;
this.id2 = id2;
}
public Integer getId1() {
return id1;
}
public void setId1(Integer id1) {
this.id1 = id1;
}
public Integer getId2() {
return id2;
}
public void setId2(Integer id2) {
this.id2 = id2;
}
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -0,0 +1,336 @@
/*
* 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;
import java.util.Set;
import java.util.function.Consumer;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
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.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromOneToManyIdClassTests.Contact.class)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromOneToManyIdClassTests {
@Test
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.asc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id1" ), a.get( "contact" ).get( "id2" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id1, a.contact.id2 from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first " +
"limit 1" +
") a " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
assertEquals( 2, list.get( 0 ).get( 2, Integer.class ) );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Integer.class ) );
assertEquals( 3, list.get( 1 ).get( 2, Integer.class ) );
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Integer.class ) );
assertNull( list.get( 2 ).get( 2, Integer.class ) );
}
);
}
);
}
@Test
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"order by c.id1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Contact contact = new Contact(
1,
new Contact.Name( "John", "Doe" )
);
final Contact alternativeContact = new Contact(
2,
new Contact.Name( "Jane", "Doe" )
);
final Contact alternativeContact2 = new Contact(
3,
new Contact.Name( "Granny", "Doe" )
);
alternativeContact2.setPrimaryContact( alternativeContact );
alternativeContact.setPrimaryContact( contact );
session.persist( contact );
session.persist( alternativeContact );
session.persist( alternativeContact2 );
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "update Contact set primaryContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
/**
* @author Steve Ebersole
*/
@Entity( name = "Contact")
@Table( name = "contacts" )
@SecondaryTable( name="contact_supp" )
public static class Contact {
private Integer id1;
private Integer id2;
private Name name;
private Set<Contact> alternativeContacts;
private Contact primaryContact;
public Contact() {
}
public Contact(Integer id, Name name) {
this.id1 = id;
this.id2 = id;
this.name = name;
}
@Id
public Integer getId1() {
return id1;
}
public void setId1(Integer id1) {
this.id1 = id1;
}
@Id
public Integer getId2() {
return id2;
}
public void setId2(Integer id2) {
this.id2 = id2;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@OneToMany(mappedBy = "primaryContact")
public Set<Contact> getAlternativeContacts() {
return alternativeContacts;
}
public void setAlternativeContacts(Set<Contact> alternativeContacts) {
this.alternativeContacts = alternativeContacts;
}
@OneToOne(fetch = FetchType.LAZY)
public Contact getPrimaryContact() {
return primaryContact;
}
public void setPrimaryContact(Contact primaryContact) {
this.primaryContact = primaryContact;
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -0,0 +1,322 @@
/*
* 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;
import java.util.Set;
import java.util.function.Consumer;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
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.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromOneToManyTests.Contact.class)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromOneToManyTests {
@Test
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.asc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 3, list.get( 1 ).get( 1, Integer.class ) );
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
assertNull( list.get( 2 ).get( 1, Integer.class ) );
}
);
}
);
}
@Test
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"join a.contact alt " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaRoot<Contact> root = cq.from( Contact.class );
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> correlatedRoot = subquery.correlate( root );
final Join<Object, Object> alternativeContacts = correlatedRoot.join( "alternativeContacts" );
subquery.multiselect( alternativeContacts.alias( "contact" ) );
subquery.orderBy( cb.desc( alternativeContacts.get( "name" ).get( "first" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContacts alt " +
"order by alt.name.first desc " +
"limit 1" +
") a " +
"order by c.id",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Contact contact = new Contact(
1,
new Contact.Name( "John", "Doe" )
);
final Contact alternativeContact = new Contact(
2,
new Contact.Name( "Jane", "Doe" )
);
final Contact alternativeContact2 = new Contact(
3,
new Contact.Name( "Granny", "Doe" )
);
alternativeContact2.setPrimaryContact( alternativeContact );
alternativeContact.setPrimaryContact( contact );
session.persist( contact );
session.persist( alternativeContact );
session.persist( alternativeContact2 );
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "update Contact set primaryContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
/**
* @author Steve Ebersole
*/
@Entity( name = "Contact")
@Table( name = "contacts" )
@SecondaryTable( name="contact_supp" )
public static class Contact {
private Integer id;
private Name name;
private Set<Contact> alternativeContacts;
private Contact primaryContact;
public Contact() {
}
public Contact(Integer id, Name name) {
this.id = id;
this.name = name;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@OneToMany(mappedBy = "primaryContact")
public Set<Contact> getAlternativeContacts() {
return alternativeContacts;
}
public void setAlternativeContacts(Set<Contact> alternativeContacts) {
this.alternativeContacts = alternativeContacts;
}
@ManyToOne(fetch = FetchType.LAZY)
public Contact getPrimaryContact() {
return primaryContact;
}
public void setPrimaryContact(Contact primaryContact) {
this.primaryContact = primaryContact;
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -10,7 +10,6 @@ import java.time.LocalDate;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
@ -22,7 +21,6 @@ import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.contacts.Address;
import org.hibernate.testing.orm.domain.contacts.Contact;
@ -84,7 +82,7 @@ public class SubQueryInFromTests {
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testBasic(SessionFactoryScope scope) {
scope.inTransaction(
@ -171,7 +169,7 @@ public class SubQueryInFromTests {
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testEmbedded(SessionFactoryScope scope) {
scope.inTransaction(
@ -254,7 +252,7 @@ public class SubQueryInFromTests {
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testEntity(SessionFactoryScope scope) {
scope.inTransaction(
@ -300,7 +298,7 @@ public class SubQueryInFromTests {
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction(
@ -348,7 +346,7 @@ public class SubQueryInFromTests {
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction(
@ -436,6 +434,7 @@ public class SubQueryInFromTests {
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "update Contact set alternativeContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).executeUpdate();
} );
}

View File

@ -14,6 +14,7 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.testing.orm.junit.DialectFeatureCheck;
@ -295,4 +296,11 @@ abstract public class DialectChecks {
&& !( dialect instanceof AbstractHANADialect );
}
}
public static class SupportsSubqueryInOnClause implements DialectCheck {
public boolean isMatch(Dialect dialect) {
// TiDB db does not support subqueries for ON condition
return !( dialect instanceof TiDBDialect );
}
}
}

View File

@ -448,4 +448,11 @@ abstract public class DialectFeatureChecks {
return dialect instanceof PostgreSQLDialect;
}
}
public static class SupportsSubqueryInOnClause implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
// TiDB db does not support subqueries for ON condition
return !( dialect instanceof TiDBDialect );
}
}
}