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

View File

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

View File

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

View File

@ -599,7 +599,7 @@ public class ToOneAttributeMapping
return null; return null;
} }
static void addPrefixedPropertyNames( public static void addPrefixedPropertyNames(
Set<String> targetKeyPropertyNames, Set<String> targetKeyPropertyNames,
String prefix, String prefix,
Type type, 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.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup; 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.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.FetchOptions; import org.hibernate.sql.results.graph.FetchOptions;
@ -186,10 +187,14 @@ public class AnonymousTupleBasicValuedModelPart implements ModelPart, MappingTyp
FetchParent fetchParent, FetchParent fetchParent,
SqlAstCreationState creationState) { SqlAstCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlExpressionResolver(); final SqlExpressionResolver expressionResolver = creationState.getSqlExpressionResolver();
final TableReference tableReference = tableGroup.resolveTableReference(
navigablePath,
getContainingTableExpression()
);
final Expression expression = expressionResolver.resolveSqlExpression( final Expression expression = expressionResolver.resolveSqlExpression(
createColumnReferenceKey( tableGroup.getPrimaryTableReference(), getSelectionExpression() ), createColumnReferenceKey( tableReference, getSelectionExpression() ),
sqlAstProcessingState -> new ColumnReference( sqlAstProcessingState -> new ColumnReference(
tableGroup.resolveTableReference( navigablePath, "" ), tableReference,
this, this,
creationState.getCreationContext().getSessionFactory() 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.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -20,12 +23,14 @@ import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.mapping.IndexedConsumer; import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping; import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityRowIdMapping; import org.hibernate.metamodel.mapping.EntityRowIdMapping;
import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart; 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.Clause;
import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess; 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.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference; 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.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer; import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
@ -70,6 +78,7 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
private final DomainType<?> domainType; private final DomainType<?> domainType;
private final String componentName; private final String componentName;
private final EntityValuedModelPart delegate; private final EntityValuedModelPart delegate;
private final Set<String> targetKeyPropertyNames;
public AnonymousTupleEntityValuedModelPart( public AnonymousTupleEntityValuedModelPart(
EntityIdentifierMapping identifierMapping, EntityIdentifierMapping identifierMapping,
@ -80,6 +89,17 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
this.domainType = domainType; this.domainType = domainType;
this.componentName = componentName; this.componentName = componentName;
this.delegate = delegate; 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 @Override
@ -206,41 +226,72 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
SqlAstCreationContext creationContext) { SqlAstCreationContext creationContext) {
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory(); final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER ); 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 SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
final TableGroup tableGroup = ( (TableGroupJoinProducer) delegate ).createRootTableGroupJoin( final boolean canUseInnerJoin = joinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins();
final EntityPersister entityPersister = delegate.getEntityMappingType().getEntityPersister();
final LazyTableGroup lazyTableGroup = new LazyTableGroup(
canUseInnerJoin,
navigablePath, navigablePath,
lhs,
explicitSourceAlias,
joinType,
fetched, fetched,
null, () -> createTableGroupInternal(
aliasBaseGenerator, canUseInnerJoin,
sqlExpressionResolver, navigablePath,
fromClauseAccess, fetched,
creationContext 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( final TableGroupJoin tableGroupJoin = new TableGroupJoin(
tableGroup.getNavigablePath(), lazyTableGroup.getNavigablePath(),
joinType, joinType,
tableGroup, lazyTableGroup,
null 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> keyMappings;
final List<SelectableMapping> targetMappings; final List<SelectableMapping> targetMappings;
if ( delegate instanceof ToOneAttributeMapping ) { if ( associationMapping.isReferenceToPrimaryKey() && associationMapping.getSideNature() == ForeignKeyDescriptor.Nature.KEY ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) this.delegate; final ModelPart targetJoinModelPart = associationMapping.getForeignKeyDescriptor()
final ModelPart targetJoinModelPart = toOneAttributeMapping.getForeignKeyDescriptor() .getPart( associationMapping.getSideNature().inverse() );
.getPart( toOneAttributeMapping.getSideNature().inverse() );
targetMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() ); targetMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() );
targetJoinModelPart.forEachSelectable( targetJoinModelPart.forEachSelectable(
0, 0,
(i, selectableMapping) -> targetMappings.add( selectableMapping ) (i, selectableMapping) -> targetMappings.add( selectableMapping )
); );
keyMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() ); keyMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() );
toOneAttributeMapping.getForeignKeyDescriptor() associationMapping.getForeignKeyDescriptor()
.getPart( toOneAttributeMapping.getSideNature() ) .getPart( associationMapping.getSideNature() )
.forEachSelectable( .forEachSelectable(
0, 0,
(i, selectableMapping) -> keyMappings.add( selectableMapping ) (i, selectableMapping) -> keyMappings.add( selectableMapping )
@ -256,44 +307,113 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
keyMappings = targetMappings; keyMappings = targetMappings;
} }
final TableReference tableReference = lhs.getPrimaryTableReference(); final TableReference tableReference = lhs.getPrimaryTableReference();
final List<ColumnReference> keyColumnReferences = new ArrayList<>( this.identifierMapping.getJdbcTypeCount() );
this.identifierMapping.forEachSelectable( this.identifierMapping.forEachSelectable(
(i, selectableMapping) -> { (i, selectableMapping) -> {
final SelectableMapping targetMapping = targetMappings.get( i ); // It is important to resolve the sql expression here,
final TableReference targetTableReference = tableGroup.resolveTableReference( // as this selectableMapping is the "derived" one.
null, // We want to register the expression under the key of the original mapping
targetMapping.getContainingTableExpression(), // which leads to this expression being used for a possible domain result
false keyColumnReferences.add(
); (ColumnReference) sqlExpressionResolver.resolveSqlExpression(
tableGroupJoin.applyPredicate( SqlExpressionResolver.createColumnReferenceKey(
new ComparisonPredicate( tableReference,
// It is important to resolve the sql expression here, keyMappings.get( i ).getSelectionExpression()
// 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
)
), ),
ComparisonOperator.EQUAL, state -> new ColumnReference(
new ColumnReference( tableReference,
targetTableReference, selectableMapping,
targetMapping,
sessionFactory 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; 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 @Override
public TableGroup createRootTableGroupJoin( public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath, NavigablePath navigablePath,
@ -436,7 +556,7 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
@Override @Override
public EntityIdentifierMapping getIdentifierMapping() { public EntityIdentifierMapping getIdentifierMapping() {
return identifierMapping; return delegate.getEntityMappingType().getIdentifierMapping();
} }
@Override @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 * @author Christian Beikov
*/ */
@Incubating @Incubating
public class AnonymousTuplePersistentSingularAttribute<O, J> extends AnonymousTupleSqmPathSource<J> implements public class AnonymousTupleSqmAssociationPathSource<O, J> extends AnonymousTupleSqmPathSource<J> implements
SingularPersistentAttribute<O, J> { SingularPersistentAttribute<O, J> {
private final SingularPersistentAttribute<O, J> delegate; private final SimpleDomainType<J> domainType;
public AnonymousTuplePersistentSingularAttribute( public AnonymousTupleSqmAssociationPathSource(
String localPathName, String localPathName,
SqmPath<J> path, SqmPath<J> path,
SingularPersistentAttribute<O, J> delegate) { SimpleDomainType<J> domainType) {
super( localPathName, path ); super( localPathName, path );
this.delegate = delegate; this.domainType = domainType;
} }
@Override @Override
@ -57,66 +57,66 @@ public class AnonymousTuplePersistentSingularAttribute<O, J> extends AnonymousTu
@Override @Override
public SimpleDomainType<J> getType() { public SimpleDomainType<J> getType() {
return delegate.getType(); return domainType;
} }
@Override @Override
public ManagedDomainType<O> getDeclaringType() { public ManagedDomainType<O> getDeclaringType() {
return delegate.getDeclaringType(); return null;
} }
@Override @Override
public boolean isId() { public boolean isId() {
return delegate.isId(); return false;
} }
@Override @Override
public boolean isVersion() { public boolean isVersion() {
return delegate.isVersion(); return false;
} }
@Override @Override
public boolean isOptional() { public boolean isOptional() {
return delegate.isOptional(); return true;
} }
@Override @Override
public JavaType<J> getAttributeJavaType() { public JavaType<J> getAttributeJavaType() {
return delegate.getAttributeJavaType(); return domainType.getExpressibleJavaType();
} }
@Override @Override
public AttributeClassification getAttributeClassification() { public AttributeClassification getAttributeClassification() {
return delegate.getAttributeClassification(); return AttributeClassification.MANY_TO_ONE;
} }
@Override @Override
public SimpleDomainType<?> getKeyGraphType() { public SimpleDomainType<?> getKeyGraphType() {
return delegate.getKeyGraphType(); return domainType;
} }
@Override @Override
public String getName() { public String getName() {
return delegate.getName(); return getPathName();
} }
@Override @Override
public PersistentAttributeType getPersistentAttributeType() { public PersistentAttributeType getPersistentAttributeType() {
return delegate.getPersistentAttributeType(); return PersistentAttributeType.MANY_TO_ONE;
} }
@Override @Override
public Member getJavaMember() { public Member getJavaMember() {
return delegate.getJavaMember(); return null;
} }
@Override @Override
public boolean isAssociation() { public boolean isAssociation() {
return delegate.isAssociation(); return true;
} }
@Override @Override
public boolean isCollection() { 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.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.IndexedConsumer; import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType; 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.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart; 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.SingleAttributeIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.model.domain.DomainType; 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.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.LazyTableGroup; 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.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupProducer; import org.hibernate.sql.ast.tree.from.TableGroupProducer;
import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResult;
@ -116,6 +120,9 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
} }
private ModelPart getModelPart(TableGroup tableGroup) { private ModelPart getModelPart(TableGroup tableGroup) {
if ( tableGroup instanceof PluralTableGroup ) {
tableGroup = ( (PluralTableGroup) tableGroup ).getElementTableGroup();
}
if ( tableGroup instanceof LazyTableGroup && ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup() != null ) { if ( tableGroup instanceof LazyTableGroup && ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup() != null ) {
return ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup().getModelPart(); return ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup().getModelPart();
} }
@ -137,13 +144,39 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
.getIdentifierMapping(); .getIdentifierMapping();
final EntityIdentifierMapping newIdentifierMapping; final EntityIdentifierMapping newIdentifierMapping;
if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) { if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) {
final String attributeName = ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName();
if ( identifierMapping.getPartMappingType() instanceof ManagedMappingType ) { if ( identifierMapping.getPartMappingType() instanceof ManagedMappingType ) {
// todo: implement //noinspection unchecked
throw new UnsupportedOperationException("Support for embedded id in anonymous tuples is not yet implemented"); 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 { else {
newIdentifierMapping = new AnonymousTupleBasicEntityIdentifierMapping( newIdentifierMapping = new AnonymousTupleBasicEntityIdentifierMapping(
selectionExpression + "_" + ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName(), selectionExpression + "_" + attributeName,
sqmExpressible, sqmExpressible,
sqlSelections.get( selectionIndex ) sqlSelections.get( selectionIndex )
.getExpressionType() .getExpressionType()
@ -154,8 +187,33 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
} }
} }
else { else {
// todo: implement //noinspection unchecked
throw new UnsupportedOperationException("Support for id-class in anonymous tuples is not yet implemented"); 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 ) { if ( existingModelPartContainer instanceof ToOneAttributeMapping ) {
// We take "ownership" of FK columns by reporting the derived table group is compatible // 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.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.UnsupportedMappingException; import org.hibernate.metamodel.UnsupportedMappingException;
import org.hibernate.metamodel.model.domain.DomainType; 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.SingularPersistentAttribute;
import org.hibernate.metamodel.model.domain.TupleType; import org.hibernate.metamodel.model.domain.TupleType;
import org.hibernate.query.ReturnableType; import org.hibernate.query.ReturnableType;
@ -126,7 +129,27 @@ public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, Retur
final SqmPath<?> sqmPath = (SqmPath<?>) component; final SqmPath<?> sqmPath = (SqmPath<?>) component;
if ( sqmPath.getNodeType() instanceof SingularPersistentAttribute<?, ?> ) { if ( sqmPath.getNodeType() instanceof SingularPersistentAttribute<?, ?> ) {
//noinspection unchecked,rawtypes //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 { else {
return new AnonymousTupleSqmPathSource<>( name, sqmPath ); 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) { private static void verifyManipulationImplicitJoin(TableGroup tableGroup) {
//noinspection StatementWithEmptyBody //noinspection StatementWithEmptyBody
if ( tableGroup instanceof LazyTableGroup && ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup() == null if ( !tableGroup.isInitialized() || tableGroup instanceof VirtualTableGroup ) {
|| tableGroup instanceof VirtualTableGroup ) {
// this is fine // this is fine
} }
else { else {
@ -1119,7 +1118,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private static boolean hasJoins(List<TableGroupJoin> tableGroupJoins) { private static boolean hasJoins(List<TableGroupJoin> tableGroupJoins) {
for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) { for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) {
final TableGroup joinedGroup = tableGroupJoin.getJoinedGroup(); final TableGroup joinedGroup = tableGroupJoin.getJoinedGroup();
if ( joinedGroup instanceof LazyTableGroup && ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup() == null ) { if ( !joinedGroup.isInitialized() ) {
continue; continue;
} }
return true; return true;
@ -3061,7 +3060,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else { else {
idPath = componentName + "_" + idAttribute.getName(); idPath = componentName + "_" + idAttribute.getName();
} }
addColumnNames( columnNames, entityDomainType.getIdType(), idPath ); addColumnNames( columnNames, entityDomainType.getIdentifierDescriptor().getSqmPathType(), idPath );
} }
else if ( domainType instanceof ManagedDomainType<?> ) { else if ( domainType instanceof ManagedDomainType<?> ) {
for ( Attribute<?, ?> attribute : ( (ManagedDomainType<?>) domainType ).getAttributes() ) { for ( Attribute<?, ?> attribute : ( (ManagedDomainType<?>) domainType ).getAttributes() ) {

View File

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

View File

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

View File

@ -200,4 +200,9 @@ public abstract class DelegatingTableGroup implements TableGroup {
public boolean isFetched() { public boolean isFetched() {
return getTableGroup().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; this.parentTableGroup = parentTableGroup;
} }
@Override
public boolean isInitialized() {
return tableGroup != null;
}
public TableGroup getUnderlyingTableGroup() { public TableGroup getUnderlyingTableGroup() {
return tableGroup; return tableGroup;
} }

View File

@ -151,4 +151,12 @@ public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, SqmPat
default boolean isFetched() { default boolean isFetched() {
return false; 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.Table;
import jakarta.persistence.Tuple; import jakarta.persistence.Tuple;
import org.hibernate.dialect.TiDBDialect; import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.SkipForDialect; import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa; import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
@ -248,7 +248,7 @@ public class OuterJoinTest {
} }
@Test @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) { public void testJoinOrderWithRightJoinWithInnerImplicitJoins(EntityManagerFactoryScope scope) {
scope.inTransaction( em -> { scope.inTransaction( em -> {
List<Tuple> resultList = em.createQuery( List<Tuple> resultList = em.createQuery(
@ -378,7 +378,7 @@ public class OuterJoinTest {
} }
@Test @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) { public void testSubQueryInOnClause(EntityManagerFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
em -> { em -> {
@ -395,7 +395,7 @@ public class OuterJoinTest {
} }
@Test @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) { public void testSubQueryCorrelationInOnClause(EntityManagerFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
em -> { em -> {

View File

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

View File

@ -8,7 +8,6 @@ package org.hibernate.orm.test.query;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery; import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin; 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.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel; 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.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; 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.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -42,18 +39,18 @@ import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/** /**
* @author Christian Beikov * @author Christian Beikov
*/ */
@DomainModel(annotatedClasses = SubQueryInFromIdClassTests.Contact.class) @DomainModel(annotatedClasses = SubQueryInFromIdClassTests.Contact.class)
@SessionFactory @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) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromIdClassTests { public class SubQueryInFromIdClassTests {
@Test @Test
@FailureExpected(reason = "Support for id class association selecting in from clause subqueries not yet supported")
public void testEntity(SessionFactoryScope scope) { public void testEntity(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
session -> { session -> {
@ -70,28 +67,34 @@ public class SubQueryInFromIdClassTests {
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT ); final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) ); cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id1" ), a.get( "contact" ).get( "id2" ) );
cq.where( root.get( "alternativeContact" ).isNotNull() ); cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery( final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id1, a.contact.id2 from Contact c " + "select c.name, a.contact.id1, a.contact.id2 from Contact c " +
"left join lateral (" + "left join lateral (" +
"select alt as contact " + "select alt as contact " +
"from c.alternativeContact alt " + "from c.alternativeContact alt " +
"order by address.name.first" + "order by alt.name.first " +
"limit 1" + "limit 1" +
") a " + ") a " +
"where c.alternativeContact is not null", "order by c.id1",
Tuple.class Tuple.class
); );
verifySame( verifySame(
session.createQuery( cq ).getResultList(), session.createQuery( cq ).getResultList(),
query.getResultList(), query.getResultList(),
list -> { list -> {
assertEquals( 1, list.size() ); assertEquals( 3, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() ); 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( 1, Integer.class ) );
assertEquals( 2, list.get( 0 ).get( 2, 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 @Test
@FailureExpected(reason = "Support for id class association selecting in from clause subqueries not yet supported")
public void testEntityJoin(SessionFactoryScope scope) { public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
session -> { session -> {
@ -118,25 +120,29 @@ public class SubQueryInFromIdClassTests {
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" ); final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
cq.multiselect( root.get( "name" ), alt.get( "name" ) ); cq.multiselect( root.get( "name" ), alt.get( "name" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery( final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, alt.name from Contact c " + "select c.name, alt.name from Contact c " +
"left join lateral (" + "left join lateral (" +
"select alt as contact " + "select alt as contact " +
"from c.alternativeContact alt " + "from c.alternativeContact alt " +
"order by alt.name.first desc" + "order by alt.name.first desc " +
"limit 1" + "limit 1" +
") a " + ") a " +
"join a.contact alt", "join a.contact alt " +
"order by c.id1",
Tuple.class Tuple.class
); );
verifySame( verifySame(
session.createQuery( cq ).getResultList(), session.createQuery( cq ).getResultList(),
query.getResultList(), query.getResultList(),
list -> { list -> {
assertEquals( 1, list.size() ); assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() ); assertEquals( "John", list.get( 0 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 0 ).get( 1, 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 @Test
@FailureExpected(reason = "Support for id class association selecting in from clause subqueries not yet supported")
public void testEntityImplicit(SessionFactoryScope scope) { public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
session -> { session -> {
@ -162,24 +167,28 @@ public class SubQueryInFromIdClassTests {
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT ); final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) ); cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
cq.orderBy( cb.asc( root.get( "id1" ) ) );
final QueryImplementor<Tuple> query = session.createQuery( final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.name from Contact c " + "select c.name, a.contact.name from Contact c " +
"left join lateral (" + "left join lateral (" +
"select alt as contact " + "select alt as contact " +
"from c.alternativeContact alt " + "from c.alternativeContact alt " +
"order by alt.name.first desc" + "order by alt.name.first desc " +
"limit 1" + "limit 1" +
") a", ") a " +
"order by c.id1",
Tuple.class Tuple.class
); );
verifySame( verifySame(
session.createQuery( cq ).getResultList(), session.createQuery( cq ).getResultList(),
query.getResultList(), query.getResultList(),
list -> { list -> {
assertEquals( 1, list.size() ); assertEquals( 2, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() ); assertEquals( "John", list.get( 0 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 0 ).get( 1, 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 @AfterEach
public void dropTestData(SessionFactoryScope scope) { public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
session.createQuery( "update Contact set alternativeContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).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.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery; import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin; 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.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.testing.orm.junit.RequiresDialectFeature; 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.StandardDomainModel;
import org.hibernate.testing.orm.domain.contacts.Address; import org.hibernate.testing.orm.domain.contacts.Address;
import org.hibernate.testing.orm.domain.contacts.Contact; import org.hibernate.testing.orm.domain.contacts.Contact;
@ -84,7 +82,7 @@ public class SubQueryInFromTests {
} }
@Test @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) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testBasic(SessionFactoryScope scope) { public void testBasic(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
@ -171,7 +169,7 @@ public class SubQueryInFromTests {
} }
@Test @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) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testEmbedded(SessionFactoryScope scope) { public void testEmbedded(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
@ -254,7 +252,7 @@ public class SubQueryInFromTests {
} }
@Test @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) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testEntity(SessionFactoryScope scope) { public void testEntity(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
@ -300,7 +298,7 @@ public class SubQueryInFromTests {
} }
@Test @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) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testEntityJoin(SessionFactoryScope scope) { public void testEntityJoin(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
@ -348,7 +346,7 @@ public class SubQueryInFromTests {
} }
@Test @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) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testEntityImplicit(SessionFactoryScope scope) { public void testEntityImplicit(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
@ -436,6 +434,7 @@ public class SubQueryInFromTests {
@AfterEach @AfterEach
public void dropTestData(SessionFactoryScope scope) { public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
session.createQuery( "update Contact set alternativeContact = null" ).executeUpdate();
session.createQuery( "delete Contact" ).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.MySQLDialect;
import org.hibernate.dialect.NationalizationSupport; import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.testing.orm.junit.DialectFeatureCheck; import org.hibernate.testing.orm.junit.DialectFeatureCheck;
@ -295,4 +296,11 @@ abstract public class DialectChecks {
&& !( dialect instanceof AbstractHANADialect ); && !( 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; 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 );
}
}
} }