HHH-15367 Lift embedded/id-class to-one selection limitation for from clause subqueries
This commit is contained in:
parent
7676af4023
commit
9c660f7e0a
|
@ -32,7 +32,6 @@ import org.hibernate.dialect.OracleDialect;
|
|||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
import org.hibernate.dialect.SQLServerDialect;
|
||||
import org.hibernate.dialect.SybaseASEDialect;
|
||||
import org.hibernate.dialect.TiDBDialect;
|
||||
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
|
||||
import org.hibernate.query.QueryProducer;
|
||||
import org.hibernate.type.StandardBasicTypes;
|
||||
|
@ -2037,7 +2036,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(value = TiDBDialect.class, comment = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(DialectChecks.SupportsSubqueryInOnClause.class)
|
||||
public void test_hql_collection_index_operator_example_3() {
|
||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
//tag::hql-collection-index-operator-example[]
|
||||
|
@ -3081,8 +3080,10 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(value = TiDBDialect.class, comment = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(DialectChecks.SupportsOrderByInCorrelatedSubquery.class)
|
||||
@RequiresDialectFeature({
|
||||
DialectChecks.SupportsSubqueryInOnClause.class,
|
||||
DialectChecks.SupportsOrderByInCorrelatedSubquery.class
|
||||
})
|
||||
public void test_hql_derived_join_example() {
|
||||
|
||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
|
|
|
@ -233,7 +233,7 @@ public abstract class AbstractCompositeIdentifierMapping
|
|||
final TableReference defaultTableReference = tableGroup.resolveTableReference( navigablePath, getContainingTableExpression() );
|
||||
getEmbeddableTypeDescriptor().forEachSelectable(
|
||||
(columnIndex, selection) -> {
|
||||
final TableReference tableReference = defaultTableReference.resolveTableReference( selection.getContainingTableExpression() ) != null
|
||||
final TableReference tableReference = getContainingTableExpression().equals( selection.getContainingTableExpression() )
|
||||
? defaultTableReference
|
||||
: tableGroup.resolveTableReference( navigablePath, selection.getContainingTableExpression() );
|
||||
final Expression columnReference = sqlAstCreationState.getSqlExpressionResolver()
|
||||
|
|
|
@ -256,7 +256,7 @@ public class EmbeddedAttributeMapping
|
|||
final TableReference defaultTableReference = tableGroup.resolveTableReference( navigablePath, getContainingTableExpression() );
|
||||
getEmbeddableTypeDescriptor().forEachSelectable(
|
||||
(columnIndex, selection) -> {
|
||||
final TableReference tableReference = defaultTableReference.resolveTableReference( selection.getContainingTableExpression() ) != null
|
||||
final TableReference tableReference = getContainingTableExpression().equals( selection.getContainingTableExpression() )
|
||||
? defaultTableReference
|
||||
: tableGroup.resolveTableReference( navigablePath, selection.getContainingTableExpression() );
|
||||
final Expression columnReference = sqlAstCreationState.getSqlExpressionResolver().resolveSqlExpression(
|
||||
|
|
|
@ -599,7 +599,7 @@ public class ToOneAttributeMapping
|
|||
return null;
|
||||
}
|
||||
|
||||
static void addPrefixedPropertyNames(
|
||||
public static void addPrefixedPropertyNames(
|
||||
Set<String> targetKeyPropertyNames,
|
||||
String prefix,
|
||||
Type type,
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.hibernate.sql.ast.spi.SqlSelection;
|
|||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.FetchOptions;
|
||||
|
@ -186,10 +187,14 @@ public class AnonymousTupleBasicValuedModelPart implements ModelPart, MappingTyp
|
|||
FetchParent fetchParent,
|
||||
SqlAstCreationState creationState) {
|
||||
final SqlExpressionResolver expressionResolver = creationState.getSqlExpressionResolver();
|
||||
final TableReference tableReference = tableGroup.resolveTableReference(
|
||||
navigablePath,
|
||||
getContainingTableExpression()
|
||||
);
|
||||
final Expression expression = expressionResolver.resolveSqlExpression(
|
||||
createColumnReferenceKey( tableGroup.getPrimaryTableReference(), getSelectionExpression() ),
|
||||
createColumnReferenceKey( tableReference, getSelectionExpression() ),
|
||||
sqlAstProcessingState -> new ColumnReference(
|
||||
tableGroup.resolveTableReference( navigablePath, "" ),
|
||||
tableReference,
|
||||
this,
|
||||
creationState.getCreationContext().getSessionFactory()
|
||||
)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -8,7 +8,10 @@ package org.hibernate.query.derived;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
@ -20,12 +23,14 @@ import org.hibernate.loader.ast.spi.NaturalIdLoader;
|
|||
import org.hibernate.mapping.IndexedConsumer;
|
||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.EntityVersionMapping;
|
||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.metamodel.mapping.MappingType;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
|
@ -42,11 +47,14 @@ import org.hibernate.spi.NavigablePath;
|
|||
import org.hibernate.sql.ast.Clause;
|
||||
import org.hibernate.sql.ast.SqlAstJoinType;
|
||||
import org.hibernate.sql.ast.spi.FromClauseAccess;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.StandardTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
||||
|
@ -70,6 +78,7 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
|
|||
private final DomainType<?> domainType;
|
||||
private final String componentName;
|
||||
private final EntityValuedModelPart delegate;
|
||||
private final Set<String> targetKeyPropertyNames;
|
||||
|
||||
public AnonymousTupleEntityValuedModelPart(
|
||||
EntityIdentifierMapping identifierMapping,
|
||||
|
@ -80,6 +89,17 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
|
|||
this.domainType = domainType;
|
||||
this.componentName = componentName;
|
||||
this.delegate = delegate;
|
||||
final EntityPersister persister = ((EntityMappingType) delegate.getPartMappingType())
|
||||
.getEntityPersister();
|
||||
final Set<String> targetKeyPropertyNames = new HashSet<>();
|
||||
targetKeyPropertyNames.add( EntityIdentifierMapping.ROLE_LOCAL_NAME );
|
||||
ToOneAttributeMapping.addPrefixedPropertyNames(
|
||||
targetKeyPropertyNames,
|
||||
persister.getIdentifierPropertyName(),
|
||||
persister.getIdentifierType(),
|
||||
persister.getFactory()
|
||||
);
|
||||
this.targetKeyPropertyNames = targetKeyPropertyNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -206,41 +226,72 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
|
|||
SqlAstCreationContext creationContext) {
|
||||
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
|
||||
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
|
||||
// Need to create a root table group and join predicate separately instead of a table group join directly,
|
||||
// because the column names on the "key-side" have different names
|
||||
final TableGroup tableGroup = ( (TableGroupJoinProducer) delegate ).createRootTableGroupJoin(
|
||||
|
||||
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
|
||||
final boolean canUseInnerJoin = joinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins();
|
||||
final EntityPersister entityPersister = delegate.getEntityMappingType().getEntityPersister();
|
||||
final LazyTableGroup lazyTableGroup = new LazyTableGroup(
|
||||
canUseInnerJoin,
|
||||
navigablePath,
|
||||
lhs,
|
||||
explicitSourceAlias,
|
||||
joinType,
|
||||
fetched,
|
||||
null,
|
||||
aliasBaseGenerator,
|
||||
sqlExpressionResolver,
|
||||
fromClauseAccess,
|
||||
creationContext
|
||||
() -> createTableGroupInternal(
|
||||
canUseInnerJoin,
|
||||
navigablePath,
|
||||
fetched,
|
||||
null,
|
||||
sqlAliasBase,
|
||||
sqlExpressionResolver,
|
||||
creationContext
|
||||
),
|
||||
(np, tableExpression) -> {
|
||||
if ( !tableExpression.isEmpty() && !entityPersister.containsTableReference( tableExpression ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( navigablePath.equals( np.getParent() ) ) {
|
||||
return targetKeyPropertyNames.contains( np.getLocalName() );
|
||||
}
|
||||
|
||||
final String relativePath = np.relativize( navigablePath );
|
||||
if ( relativePath == null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Empty relative path means the navigable paths are equal,
|
||||
// in which case we allow resolving the parent table group
|
||||
return relativePath.isEmpty() || targetKeyPropertyNames.contains( relativePath );
|
||||
},
|
||||
this,
|
||||
explicitSourceAlias,
|
||||
sqlAliasBase,
|
||||
creationContext.getSessionFactory(),
|
||||
lhs
|
||||
);
|
||||
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
|
||||
tableGroup.getNavigablePath(),
|
||||
lazyTableGroup.getNavigablePath(),
|
||||
joinType,
|
||||
tableGroup,
|
||||
lazyTableGroup,
|
||||
null
|
||||
);
|
||||
|
||||
// -----------------
|
||||
// Collect the selectable mappings for the FK key side and target side
|
||||
// As we will "resolve" the derived column references for these mappings
|
||||
// --------------
|
||||
|
||||
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) delegate;
|
||||
final List<SelectableMapping> keyMappings;
|
||||
final List<SelectableMapping> targetMappings;
|
||||
if ( delegate instanceof ToOneAttributeMapping ) {
|
||||
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) this.delegate;
|
||||
final ModelPart targetJoinModelPart = toOneAttributeMapping.getForeignKeyDescriptor()
|
||||
.getPart( toOneAttributeMapping.getSideNature().inverse() );
|
||||
if ( associationMapping.isReferenceToPrimaryKey() && associationMapping.getSideNature() == ForeignKeyDescriptor.Nature.KEY ) {
|
||||
final ModelPart targetJoinModelPart = associationMapping.getForeignKeyDescriptor()
|
||||
.getPart( associationMapping.getSideNature().inverse() );
|
||||
targetMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() );
|
||||
targetJoinModelPart.forEachSelectable(
|
||||
0,
|
||||
(i, selectableMapping) -> targetMappings.add( selectableMapping )
|
||||
);
|
||||
keyMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() );
|
||||
toOneAttributeMapping.getForeignKeyDescriptor()
|
||||
.getPart( toOneAttributeMapping.getSideNature() )
|
||||
associationMapping.getForeignKeyDescriptor()
|
||||
.getPart( associationMapping.getSideNature() )
|
||||
.forEachSelectable(
|
||||
0,
|
||||
(i, selectableMapping) -> keyMappings.add( selectableMapping )
|
||||
|
@ -256,44 +307,113 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
|
|||
keyMappings = targetMappings;
|
||||
}
|
||||
final TableReference tableReference = lhs.getPrimaryTableReference();
|
||||
final List<ColumnReference> keyColumnReferences = new ArrayList<>( this.identifierMapping.getJdbcTypeCount() );
|
||||
this.identifierMapping.forEachSelectable(
|
||||
(i, selectableMapping) -> {
|
||||
final SelectableMapping targetMapping = targetMappings.get( i );
|
||||
final TableReference targetTableReference = tableGroup.resolveTableReference(
|
||||
null,
|
||||
targetMapping.getContainingTableExpression(),
|
||||
false
|
||||
);
|
||||
tableGroupJoin.applyPredicate(
|
||||
new ComparisonPredicate(
|
||||
// It is important to resolve the sql expression here,
|
||||
// as this selectableMapping is the "derived" one.
|
||||
// We want to register the expression under the key of the original mapping
|
||||
// which leads to this expression being used for a possible domain result
|
||||
sqlExpressionResolver.resolveSqlExpression(
|
||||
SqlExpressionResolver.createColumnReferenceKey(
|
||||
tableReference,
|
||||
keyMappings.get( i ).getSelectionExpression()
|
||||
),
|
||||
state -> new ColumnReference(
|
||||
tableReference,
|
||||
selectableMapping,
|
||||
sessionFactory
|
||||
)
|
||||
// It is important to resolve the sql expression here,
|
||||
// as this selectableMapping is the "derived" one.
|
||||
// We want to register the expression under the key of the original mapping
|
||||
// which leads to this expression being used for a possible domain result
|
||||
keyColumnReferences.add(
|
||||
(ColumnReference) sqlExpressionResolver.resolveSqlExpression(
|
||||
SqlExpressionResolver.createColumnReferenceKey(
|
||||
tableReference,
|
||||
keyMappings.get( i ).getSelectionExpression()
|
||||
),
|
||||
ComparisonOperator.EQUAL,
|
||||
new ColumnReference(
|
||||
targetTableReference,
|
||||
targetMapping,
|
||||
state -> new ColumnReference(
|
||||
tableReference,
|
||||
selectableMapping,
|
||||
sessionFactory
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
if ( keyMappings != targetMappings ) {
|
||||
this.identifierMapping.forEachSelectable(
|
||||
(i, selectableMapping) -> {
|
||||
// It is important to resolve the sql expression here,
|
||||
// as this selectableMapping is the "derived" one.
|
||||
// We want to register the expression under the key of the original mapping
|
||||
// which leads to this expression being used for a possible domain result
|
||||
sqlExpressionResolver.resolveSqlExpression(
|
||||
SqlExpressionResolver.createColumnReferenceKey(
|
||||
tableReference,
|
||||
targetMappings.get( i ).getSelectionExpression()
|
||||
),
|
||||
state -> new ColumnReference(
|
||||
tableReference,
|
||||
selectableMapping,
|
||||
sessionFactory
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
lazyTableGroup.setTableGroupInitializerCallback(
|
||||
tg -> {
|
||||
this.identifierMapping.forEachSelectable(
|
||||
(i, selectableMapping) -> {
|
||||
final SelectableMapping targetMapping = targetMappings.get( i );
|
||||
final TableReference targetTableReference = tg.resolveTableReference(
|
||||
null,
|
||||
targetMapping.getContainingTableExpression(),
|
||||
false
|
||||
);
|
||||
tableGroupJoin.applyPredicate(
|
||||
new ComparisonPredicate(
|
||||
keyColumnReferences.get( i ),
|
||||
ComparisonOperator.EQUAL,
|
||||
new ColumnReference(
|
||||
targetTableReference,
|
||||
targetMapping,
|
||||
sessionFactory
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
return tableGroupJoin;
|
||||
}
|
||||
|
||||
public TableGroup createTableGroupInternal(
|
||||
boolean canUseInnerJoins,
|
||||
NavigablePath navigablePath,
|
||||
boolean fetched,
|
||||
String sourceAlias,
|
||||
final SqlAliasBase sqlAliasBase,
|
||||
SqlExpressionResolver sqlExpressionResolver,
|
||||
SqlAstCreationContext creationContext) {
|
||||
final EntityMappingType entityMappingType = delegate.getEntityMappingType();
|
||||
final TableReference primaryTableReference = entityMappingType.createPrimaryTableReference(
|
||||
sqlAliasBase,
|
||||
sqlExpressionResolver,
|
||||
creationContext
|
||||
);
|
||||
|
||||
return new StandardTableGroup(
|
||||
canUseInnerJoins,
|
||||
navigablePath,
|
||||
this,
|
||||
fetched,
|
||||
sourceAlias,
|
||||
primaryTableReference,
|
||||
true,
|
||||
sqlAliasBase,
|
||||
entityMappingType::containsTableReference,
|
||||
(tableExpression, tg) -> entityMappingType.createTableReferenceJoin(
|
||||
tableExpression,
|
||||
sqlAliasBase,
|
||||
primaryTableReference,
|
||||
sqlExpressionResolver,
|
||||
creationContext
|
||||
),
|
||||
creationContext.getSessionFactory()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
|
@ -436,7 +556,7 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
|
|||
|
||||
@Override
|
||||
public EntityIdentifierMapping getIdentifierMapping() {
|
||||
return identifierMapping;
|
||||
return delegate.getEntityMappingType().getIdentifierMapping();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -25,17 +25,17 @@ import org.hibernate.type.descriptor.java.JavaType;
|
|||
* @author Christian Beikov
|
||||
*/
|
||||
@Incubating
|
||||
public class AnonymousTuplePersistentSingularAttribute<O, J> extends AnonymousTupleSqmPathSource<J> implements
|
||||
public class AnonymousTupleSqmAssociationPathSource<O, J> extends AnonymousTupleSqmPathSource<J> implements
|
||||
SingularPersistentAttribute<O, J> {
|
||||
|
||||
private final SingularPersistentAttribute<O, J> delegate;
|
||||
private final SimpleDomainType<J> domainType;
|
||||
|
||||
public AnonymousTuplePersistentSingularAttribute(
|
||||
public AnonymousTupleSqmAssociationPathSource(
|
||||
String localPathName,
|
||||
SqmPath<J> path,
|
||||
SingularPersistentAttribute<O, J> delegate) {
|
||||
SimpleDomainType<J> domainType) {
|
||||
super( localPathName, path );
|
||||
this.delegate = delegate;
|
||||
this.domainType = domainType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -57,66 +57,66 @@ public class AnonymousTuplePersistentSingularAttribute<O, J> extends AnonymousTu
|
|||
|
||||
@Override
|
||||
public SimpleDomainType<J> getType() {
|
||||
return delegate.getType();
|
||||
return domainType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedDomainType<O> getDeclaringType() {
|
||||
return delegate.getDeclaringType();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isId() {
|
||||
return delegate.isId();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVersion() {
|
||||
return delegate.isVersion();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptional() {
|
||||
return delegate.isOptional();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType<J> getAttributeJavaType() {
|
||||
return delegate.getAttributeJavaType();
|
||||
return domainType.getExpressibleJavaType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeClassification getAttributeClassification() {
|
||||
return delegate.getAttributeClassification();
|
||||
return AttributeClassification.MANY_TO_ONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleDomainType<?> getKeyGraphType() {
|
||||
return delegate.getKeyGraphType();
|
||||
return domainType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return delegate.getName();
|
||||
return getPathName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentAttributeType getPersistentAttributeType() {
|
||||
return delegate.getPersistentAttributeType();
|
||||
return PersistentAttributeType.MANY_TO_ONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Member getJavaMember() {
|
||||
return delegate.getJavaMember();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAssociation() {
|
||||
return delegate.isAssociation();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollection() {
|
||||
return delegate.isCollection();
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.mapping.IndexedConsumer;
|
||||
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
|
@ -26,6 +27,8 @@ import org.hibernate.metamodel.mapping.JdbcMapping;
|
|||
import org.hibernate.metamodel.mapping.ManagedMappingType;
|
||||
import org.hibernate.metamodel.mapping.MappingType;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
|
@ -41,6 +44,7 @@ import org.hibernate.sql.ast.Clause;
|
|||
import org.hibernate.sql.ast.spi.FromClauseAccess;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.PluralTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupProducer;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
|
@ -116,6 +120,9 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
|
|||
}
|
||||
|
||||
private ModelPart getModelPart(TableGroup tableGroup) {
|
||||
if ( tableGroup instanceof PluralTableGroup ) {
|
||||
tableGroup = ( (PluralTableGroup) tableGroup ).getElementTableGroup();
|
||||
}
|
||||
if ( tableGroup instanceof LazyTableGroup && ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup() != null ) {
|
||||
return ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup().getModelPart();
|
||||
}
|
||||
|
@ -137,13 +144,39 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
|
|||
.getIdentifierMapping();
|
||||
final EntityIdentifierMapping newIdentifierMapping;
|
||||
if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) {
|
||||
final String attributeName = ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName();
|
||||
if ( identifierMapping.getPartMappingType() instanceof ManagedMappingType ) {
|
||||
// todo: implement
|
||||
throw new UnsupportedOperationException("Support for embedded id in anonymous tuples is not yet implemented");
|
||||
//noinspection unchecked
|
||||
final Set<Attribute<?, ?>> attributes = (Set<Attribute<?, ?>>) ( (ManagedDomainType<?>) ( (EntityDomainType<?>) domainType ).getIdentifierDescriptor().getSqmPathType() ).getAttributes();
|
||||
final Map<String, ModelPart> modelParts = CollectionHelper.linkedMapOfSize( attributes.size() );
|
||||
final EmbeddableValuedModelPart modelPartContainer = (EmbeddableValuedModelPart) identifierMapping;
|
||||
for ( Attribute<?, ?> attribute : attributes ) {
|
||||
if ( !( attribute instanceof SingularPersistentAttribute<?, ?> ) ) {
|
||||
throw new IllegalArgumentException( "Only embeddables without collections are supported!" );
|
||||
}
|
||||
final DomainType<?> attributeType = ( (SingularPersistentAttribute<?, ?>) attribute ).getType();
|
||||
final ModelPart modelPart = createModelPart(
|
||||
sqmExpressible,
|
||||
attributeType,
|
||||
sqlSelections,
|
||||
selectionIndex,
|
||||
selectionExpression + "_" + attributeName + "_" + attribute.getName(),
|
||||
attribute.getName(),
|
||||
modelPartContainer.findSubPart( attribute.getName(), null ),
|
||||
compatibleTableExpressions
|
||||
);
|
||||
modelParts.put( modelPart.getPartName(), modelPart );
|
||||
}
|
||||
newIdentifierMapping = new AnonymousTupleEmbeddedEntityIdentifierMapping(
|
||||
modelParts,
|
||||
domainType,
|
||||
attributeName,
|
||||
(CompositeIdentifierMapping) identifierMapping
|
||||
);
|
||||
}
|
||||
else {
|
||||
newIdentifierMapping = new AnonymousTupleBasicEntityIdentifierMapping(
|
||||
selectionExpression + "_" + ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName(),
|
||||
selectionExpression + "_" + attributeName,
|
||||
sqmExpressible,
|
||||
sqlSelections.get( selectionIndex )
|
||||
.getExpressionType()
|
||||
|
@ -154,8 +187,33 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
|
|||
}
|
||||
}
|
||||
else {
|
||||
// todo: implement
|
||||
throw new UnsupportedOperationException("Support for id-class in anonymous tuples is not yet implemented");
|
||||
//noinspection unchecked
|
||||
final Set<Attribute<?, ?>> attributes = (Set<Attribute<?, ?>>) ( (ManagedDomainType<?>) ( (EntityDomainType<?>) domainType ).getIdentifierDescriptor().getSqmPathType() ).getAttributes();
|
||||
final Map<String, ModelPart> modelParts = CollectionHelper.linkedMapOfSize( attributes.size() );
|
||||
final EmbeddableValuedModelPart modelPartContainer = (EmbeddableValuedModelPart) identifierMapping;
|
||||
for ( Attribute<?, ?> attribute : attributes ) {
|
||||
if ( !( attribute instanceof SingularPersistentAttribute<?, ?> ) ) {
|
||||
throw new IllegalArgumentException( "Only embeddables without collections are supported!" );
|
||||
}
|
||||
final DomainType<?> attributeType = ( (SingularPersistentAttribute<?, ?>) attribute ).getType();
|
||||
final ModelPart modelPart = createModelPart(
|
||||
sqmExpressible,
|
||||
attributeType,
|
||||
sqlSelections,
|
||||
selectionIndex,
|
||||
selectionExpression + "_" + attribute.getName(),
|
||||
attribute.getName(),
|
||||
modelPartContainer.findSubPart( attribute.getName(), null ),
|
||||
compatibleTableExpressions
|
||||
);
|
||||
modelParts.put( modelPart.getPartName(), modelPart );
|
||||
}
|
||||
newIdentifierMapping = new AnonymousTupleNonAggregatedEntityIdentifierMapping(
|
||||
modelParts,
|
||||
domainType,
|
||||
selectionExpression,
|
||||
(NonAggregatedIdentifierMapping) identifierMapping
|
||||
);
|
||||
}
|
||||
if ( existingModelPartContainer instanceof ToOneAttributeMapping ) {
|
||||
// We take "ownership" of FK columns by reporting the derived table group is compatible
|
||||
|
|
|
@ -15,6 +15,9 @@ import org.hibernate.Incubating;
|
|||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
|
||||
import org.hibernate.metamodel.model.domain.SimpleDomainType;
|
||||
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
|
||||
import org.hibernate.metamodel.model.domain.TupleType;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
|
@ -126,7 +129,27 @@ public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, Retur
|
|||
final SqmPath<?> sqmPath = (SqmPath<?>) component;
|
||||
if ( sqmPath.getNodeType() instanceof SingularPersistentAttribute<?, ?> ) {
|
||||
//noinspection unchecked,rawtypes
|
||||
return new AnonymousTuplePersistentSingularAttribute( name, sqmPath, (SingularPersistentAttribute<?, ?>) sqmPath.getNodeType() );
|
||||
return new AnonymousTupleSqmAssociationPathSource(
|
||||
name,
|
||||
sqmPath,
|
||||
( (SingularPersistentAttribute<?, ?>) sqmPath.getNodeType() ).getType()
|
||||
);
|
||||
}
|
||||
else if ( sqmPath.getNodeType() instanceof PluralPersistentAttribute<?, ?, ?> ) {
|
||||
//noinspection unchecked,rawtypes
|
||||
return new AnonymousTupleSqmAssociationPathSource(
|
||||
name,
|
||||
sqmPath,
|
||||
( (PluralPersistentAttribute<?, ?, ?>) sqmPath.getNodeType() ).getElementType()
|
||||
);
|
||||
}
|
||||
else if ( sqmPath.getNodeType() instanceof EntityDomainType<?> ) {
|
||||
//noinspection unchecked,rawtypes
|
||||
return new AnonymousTupleSqmAssociationPathSource(
|
||||
name,
|
||||
sqmPath,
|
||||
(SimpleDomainType<?>) sqmPath.getNodeType()
|
||||
);
|
||||
}
|
||||
else {
|
||||
return new AnonymousTupleSqmPathSource<>( name, sqmPath );
|
||||
|
|
|
@ -799,8 +799,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
|
||||
private static void verifyManipulationImplicitJoin(TableGroup tableGroup) {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if ( tableGroup instanceof LazyTableGroup && ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup() == null
|
||||
|| tableGroup instanceof VirtualTableGroup ) {
|
||||
if ( !tableGroup.isInitialized() || tableGroup instanceof VirtualTableGroup ) {
|
||||
// this is fine
|
||||
}
|
||||
else {
|
||||
|
@ -1119,7 +1118,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
private static boolean hasJoins(List<TableGroupJoin> tableGroupJoins) {
|
||||
for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) {
|
||||
final TableGroup joinedGroup = tableGroupJoin.getJoinedGroup();
|
||||
if ( joinedGroup instanceof LazyTableGroup && ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup() == null ) {
|
||||
if ( !joinedGroup.isInitialized() ) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
|
@ -3061,7 +3060,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
else {
|
||||
idPath = componentName + "_" + idAttribute.getName();
|
||||
}
|
||||
addColumnNames( columnNames, entityDomainType.getIdType(), idPath );
|
||||
addColumnNames( columnNames, entityDomainType.getIdentifierDescriptor().getSqmPathType(), idPath );
|
||||
}
|
||||
else if ( domainType instanceof ManagedDomainType<?> ) {
|
||||
for ( Attribute<?, ?> attribute : ( (ManagedDomainType<?>) domainType ).getAttributes() ) {
|
||||
|
|
|
@ -139,11 +139,7 @@ public class SqlTreePrinter {
|
|||
}
|
||||
|
||||
private void logTableGroupDetails(TableGroup tableGroup) {
|
||||
if ( tableGroup instanceof LazyTableGroup ) {
|
||||
TableGroup underlyingTableGroup = ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup();
|
||||
if ( underlyingTableGroup != null ) {
|
||||
logTableGroupDetails( underlyingTableGroup );
|
||||
}
|
||||
if ( !tableGroup.isInitialized() ) {
|
||||
return;
|
||||
}
|
||||
if ( tableGroup.getPrimaryTableReference() instanceof NamedTableReference ) {
|
||||
|
|
|
@ -3135,6 +3135,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
}
|
||||
else {
|
||||
int offset = 0;
|
||||
for ( int i = 0; i < size; i++ ) {
|
||||
final SqlSelection sqlSelection = sqlSelections.get( i );
|
||||
appendSql( separator );
|
||||
|
@ -3144,10 +3145,10 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
else {
|
||||
parameterRenderingMode = defaultRenderingMode;
|
||||
}
|
||||
visitSqlSelection( sqlSelection );
|
||||
offset += visitSqlSelectExpression( sqlSelection.getExpression(), offset, columnAliases );
|
||||
parameterRenderingMode = original;
|
||||
appendSql( WHITESPACE );
|
||||
appendSql( columnAliases.get( i ) );
|
||||
appendSql( columnAliases.get( offset - 1 ) );
|
||||
separator = COMA_SEPARATOR;
|
||||
}
|
||||
}
|
||||
|
@ -3173,6 +3174,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
else {
|
||||
String separator = NO_SEPARATOR;
|
||||
int offset = 0;
|
||||
for ( int i = 0; i < size; i++ ) {
|
||||
final SqlSelection sqlSelection = sqlSelections.get( i );
|
||||
appendSql( separator );
|
||||
|
@ -3182,9 +3184,9 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
else {
|
||||
parameterRenderingMode = defaultRenderingMode;
|
||||
}
|
||||
visitSqlSelection( sqlSelection );
|
||||
offset += visitSqlSelectExpression( sqlSelection.getExpression(), offset, columnAliases );
|
||||
appendSql( WHITESPACE );
|
||||
appendSql( columnAliases.get( i ) );
|
||||
appendSql( columnAliases.get( offset - 1 ) );
|
||||
parameterRenderingMode = original;
|
||||
separator = COMA_SEPARATOR;
|
||||
}
|
||||
|
@ -3537,6 +3539,32 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
}
|
||||
|
||||
protected int visitSqlSelectExpression(Expression expression, int offset, List<String> columnAliases) {
|
||||
final SqlTuple sqlTuple = SqlTupleContainer.getSqlTuple( expression );
|
||||
if ( sqlTuple != null ) {
|
||||
boolean isFirst = true;
|
||||
final List<? extends Expression> expressions = sqlTuple.getExpressions();
|
||||
int i = 0;
|
||||
for ( ; i < expressions.size(); i++ ) {
|
||||
Expression e = expressions.get( i );
|
||||
if ( isFirst ) {
|
||||
isFirst = false;
|
||||
}
|
||||
else {
|
||||
appendSql( WHITESPACE );
|
||||
appendSql( columnAliases.get( offset + i - 1 ) );
|
||||
appendSql( ',' );
|
||||
}
|
||||
renderSelectExpression( e );
|
||||
}
|
||||
return i;
|
||||
}
|
||||
else {
|
||||
renderSelectExpression( expression );
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected void renderSelectExpression(Expression expression) {
|
||||
renderExpressionAsClauseItem( expression );
|
||||
}
|
||||
|
@ -3805,19 +3833,15 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
protected boolean hasNestedTableGroupsToRender(List<TableGroupJoin> nestedTableGroupJoins) {
|
||||
for ( TableGroupJoin nestedTableGroupJoin : nestedTableGroupJoins ) {
|
||||
final TableGroup joinedGroup = nestedTableGroupJoin.getJoinedGroup();
|
||||
final TableGroup realTableGroup;
|
||||
if ( joinedGroup instanceof LazyTableGroup ) {
|
||||
realTableGroup = ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup();
|
||||
if ( !joinedGroup.isInitialized() ) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
realTableGroup = joinedGroup;
|
||||
}
|
||||
if ( realTableGroup instanceof VirtualTableGroup ) {
|
||||
if ( hasNestedTableGroupsToRender( realTableGroup.getNestedTableGroupJoins() ) ) {
|
||||
if ( joinedGroup instanceof VirtualTableGroup ) {
|
||||
if ( hasNestedTableGroupsToRender( joinedGroup.getNestedTableGroupJoins() ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ( realTableGroup != null ) {
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -3998,24 +4022,17 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
|
||||
protected void processTableGroupJoin(TableGroupJoin tableGroupJoin, List<TableGroupJoin> tableGroupJoinCollector) {
|
||||
final TableGroup joinedGroup = tableGroupJoin.getJoinedGroup();
|
||||
final TableGroup realTableGroup;
|
||||
if ( joinedGroup instanceof LazyTableGroup ) {
|
||||
realTableGroup = ( (LazyTableGroup) joinedGroup ).getUnderlyingTableGroup();
|
||||
}
|
||||
else {
|
||||
realTableGroup = joinedGroup;
|
||||
}
|
||||
|
||||
if ( realTableGroup instanceof VirtualTableGroup ) {
|
||||
processNestedTableGroupJoins( realTableGroup, tableGroupJoinCollector );
|
||||
if ( joinedGroup instanceof VirtualTableGroup ) {
|
||||
processNestedTableGroupJoins( joinedGroup, tableGroupJoinCollector );
|
||||
if ( tableGroupJoinCollector != null ) {
|
||||
tableGroupJoinCollector.addAll( realTableGroup.getTableGroupJoins() );
|
||||
tableGroupJoinCollector.addAll( joinedGroup.getTableGroupJoins() );
|
||||
}
|
||||
else {
|
||||
processTableGroupJoins( realTableGroup );
|
||||
processTableGroupJoins( joinedGroup );
|
||||
}
|
||||
}
|
||||
else if ( realTableGroup != null ) {
|
||||
else if ( joinedGroup.isInitialized() ) {
|
||||
renderTableGroupJoin(
|
||||
tableGroupJoin,
|
||||
tableGroupJoinCollector
|
||||
|
|
|
@ -200,4 +200,9 @@ public abstract class DelegatingTableGroup implements TableGroup {
|
|||
public boolean isFetched() {
|
||||
return getTableGroup().isFetched();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return getTableGroup().isInitialized();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,11 @@ public class LazyTableGroup extends DelegatingTableGroup {
|
|||
this.parentTableGroup = parentTableGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return tableGroup != null;
|
||||
}
|
||||
|
||||
public TableGroup getUnderlyingTableGroup() {
|
||||
return tableGroup;
|
||||
}
|
||||
|
|
|
@ -151,4 +151,12 @@ public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, SqmPat
|
|||
default boolean isFetched() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a lazy table group, it may report that it is not initialized,
|
||||
* which would also mean that a join referring to this table group should not be rendered.
|
||||
*/
|
||||
default boolean isInitialized() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ import jakarta.persistence.PrimaryKeyJoinColumn;
|
|||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Tuple;
|
||||
|
||||
import org.hibernate.dialect.TiDBDialect;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
@ -248,7 +248,7 @@ public class OuterJoinTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
|
||||
public void testJoinOrderWithRightJoinWithInnerImplicitJoins(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction( em -> {
|
||||
List<Tuple> resultList = em.createQuery(
|
||||
|
@ -378,7 +378,7 @@ public class OuterJoinTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
|
||||
public void testSubQueryInOnClause(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
em -> {
|
||||
|
@ -395,7 +395,7 @@ public class OuterJoinTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
|
||||
public void testSubQueryCorrelationInOnClause(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
em -> {
|
||||
|
|
|
@ -6,10 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.orm.test.query;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.dialect.TiDBDialect;
|
||||
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
||||
import org.hibernate.query.criteria.JpaCriteriaQuery;
|
||||
import org.hibernate.query.criteria.JpaDerivedJoin;
|
||||
|
@ -19,13 +17,12 @@ import org.hibernate.query.spi.QueryImplementor;
|
|||
import org.hibernate.query.sqm.tree.SqmJoinType;
|
||||
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.FailureExpected;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -43,18 +40,19 @@ import jakarta.persistence.criteria.Join;
|
|||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
@DomainModel(annotatedClasses = SubQueryInFromEmbeddedIdTests.Contact.class)
|
||||
@SessionFactory
|
||||
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||
@TestForIssue( jiraKey = "HHH-")
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
|
||||
public class SubQueryInFromEmbeddedIdTests {
|
||||
|
||||
@Test
|
||||
@FailureExpected(reason = "Support for embedded id association selecting in from clause subqueries not yet supported")
|
||||
public void testEntity(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -72,27 +70,32 @@ public class SubQueryInFromEmbeddedIdTests {
|
|||
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
|
||||
|
||||
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) );
|
||||
cq.where( root.get( "alternativeContact" ).isNotNull() );
|
||||
cq.orderBy( cb.asc( root.get( "id" ) ) );
|
||||
|
||||
final QueryImplementor<Tuple> query = session.createQuery(
|
||||
"select c.name, a.contact.id from Contact c " +
|
||||
"left join lateral (" +
|
||||
"select alt as contact " +
|
||||
"from c.alternativeContact alt " +
|
||||
"order by address.name.first" +
|
||||
"order by alt.name.first " +
|
||||
"limit 1" +
|
||||
") a " +
|
||||
"where c.alternativeContact is not null",
|
||||
"order by c.id",
|
||||
Tuple.class
|
||||
);
|
||||
verifySame(
|
||||
session.createQuery( cq ).getResultList(),
|
||||
query.getResultList(),
|
||||
list -> {
|
||||
assertEquals( 1, list.size() );
|
||||
assertEquals( 3, list.size() );
|
||||
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
|
||||
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId1() );
|
||||
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId2() );
|
||||
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
|
||||
assertEquals( 3, list.get( 1 ).get( 1, Contact.ContactId.class ).getId1() );
|
||||
assertEquals( 3, list.get( 1 ).get( 1, Contact.ContactId.class ).getId2() );
|
||||
assertEquals( "Granny", list.get( 2 ).get( 0, Contact.Name.class ).getFirst() );
|
||||
assertNull( list.get( 2 ).get( 1, Contact.ContactId.class ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -100,7 +103,6 @@ public class SubQueryInFromEmbeddedIdTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailureExpected(reason = "Support for embedded id association selecting in from clause subqueries not yet supported")
|
||||
public void testEntityJoin(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -119,25 +121,29 @@ public class SubQueryInFromEmbeddedIdTests {
|
|||
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
|
||||
|
||||
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
|
||||
cq.orderBy( cb.asc( root.get( "id" ) ) );
|
||||
|
||||
final QueryImplementor<Tuple> query = session.createQuery(
|
||||
"select c.name, alt.name from Contact c " +
|
||||
"left join lateral (" +
|
||||
"select alt as contact " +
|
||||
"from c.alternativeContact alt " +
|
||||
"order by alt.name.first desc" +
|
||||
"order by alt.name.first desc " +
|
||||
"limit 1" +
|
||||
") a " +
|
||||
"join a.contact alt",
|
||||
"join a.contact alt " +
|
||||
"order by c.id",
|
||||
Tuple.class
|
||||
);
|
||||
verifySame(
|
||||
session.createQuery( cq ).getResultList(),
|
||||
query.getResultList(),
|
||||
list -> {
|
||||
assertEquals( 1, list.size() );
|
||||
assertEquals( 2, list.size() );
|
||||
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -145,7 +151,6 @@ public class SubQueryInFromEmbeddedIdTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailureExpected(reason = "Support for embedded id association selecting in from clause subqueries not yet supported")
|
||||
public void testEntityImplicit(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -163,24 +168,28 @@ public class SubQueryInFromEmbeddedIdTests {
|
|||
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
|
||||
|
||||
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
|
||||
cq.orderBy( cb.asc( root.get( "id" ) ) );
|
||||
|
||||
final QueryImplementor<Tuple> query = session.createQuery(
|
||||
"select c.name, a.contact.name from Contact c " +
|
||||
"left join lateral (" +
|
||||
"select alt as contact " +
|
||||
"from c.alternativeContact alt " +
|
||||
"order by alt.name.first desc" +
|
||||
"order by alt.name.first desc " +
|
||||
"limit 1" +
|
||||
") a",
|
||||
") a " +
|
||||
"order by c.id",
|
||||
Tuple.class
|
||||
);
|
||||
verifySame(
|
||||
session.createQuery( cq ).getResultList(),
|
||||
query.getResultList(),
|
||||
list -> {
|
||||
assertEquals( 1, list.size() );
|
||||
assertEquals( 2, list.size() );
|
||||
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Jane", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Granny", list.get( 1 ).get( 1, Contact.Name.class ).getFirst() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -213,6 +222,7 @@ public class SubQueryInFromEmbeddedIdTests {
|
|||
@AfterEach
|
||||
public void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
session.createQuery( "update Contact set alternativeContact = null" ).executeUpdate();
|
||||
session.createQuery( "delete Contact" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ package org.hibernate.orm.test.query;
|
|||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.dialect.TiDBDialect;
|
||||
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
||||
import org.hibernate.query.criteria.JpaCriteriaQuery;
|
||||
import org.hibernate.query.criteria.JpaDerivedJoin;
|
||||
|
@ -20,11 +19,9 @@ import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
|
|||
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.FailureExpected;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -42,18 +39,18 @@ import jakarta.persistence.criteria.Join;
|
|||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
@DomainModel(annotatedClasses = SubQueryInFromIdClassTests.Contact.class)
|
||||
@SessionFactory
|
||||
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
|
||||
public class SubQueryInFromIdClassTests {
|
||||
|
||||
@Test
|
||||
@FailureExpected(reason = "Support for id class association selecting in from clause subqueries not yet supported")
|
||||
public void testEntity(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -70,28 +67,34 @@ public class SubQueryInFromIdClassTests {
|
|||
|
||||
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
|
||||
|
||||
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id" ) );
|
||||
cq.where( root.get( "alternativeContact" ).isNotNull() );
|
||||
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "id1" ), a.get( "contact" ).get( "id2" ) );
|
||||
cq.orderBy( cb.asc( root.get( "id1" ) ) );
|
||||
|
||||
final QueryImplementor<Tuple> query = session.createQuery(
|
||||
"select c.name, a.contact.id1, a.contact.id2 from Contact c " +
|
||||
"left join lateral (" +
|
||||
"select alt as contact " +
|
||||
"from c.alternativeContact alt " +
|
||||
"order by address.name.first" +
|
||||
"order by alt.name.first " +
|
||||
"limit 1" +
|
||||
") a " +
|
||||
"where c.alternativeContact is not null",
|
||||
"order by c.id1",
|
||||
Tuple.class
|
||||
);
|
||||
verifySame(
|
||||
session.createQuery( cq ).getResultList(),
|
||||
query.getResultList(),
|
||||
list -> {
|
||||
assertEquals( 1, list.size() );
|
||||
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
|
||||
assertEquals( 3, list.size() );
|
||||
assertEquals( "John", list.get( 0 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
|
||||
assertEquals( 2, list.get( 0 ).get( 2, Integer.class ) );
|
||||
assertEquals( "Jane", list.get( 1 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
assertEquals( 3, list.get( 1 ).get( 1, Integer.class ) );
|
||||
assertEquals( 3, list.get( 1 ).get( 2, Integer.class ) );
|
||||
assertEquals( "Granny", list.get( 2 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
assertNull( list.get( 2 ).get( 1, Integer.class ) );
|
||||
assertNull( list.get( 2 ).get( 2, Integer.class ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -99,7 +102,6 @@ public class SubQueryInFromIdClassTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailureExpected(reason = "Support for id class association selecting in from clause subqueries not yet supported")
|
||||
public void testEntityJoin(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -118,25 +120,29 @@ public class SubQueryInFromIdClassTests {
|
|||
final SqmAttributeJoin<Object, Object> alt = a.join( "contact" );
|
||||
|
||||
cq.multiselect( root.get( "name" ), alt.get( "name" ) );
|
||||
cq.orderBy( cb.asc( root.get( "id1" ) ) );
|
||||
|
||||
final QueryImplementor<Tuple> query = session.createQuery(
|
||||
"select c.name, alt.name from Contact c " +
|
||||
"left join lateral (" +
|
||||
"select alt as contact " +
|
||||
"from c.alternativeContact alt " +
|
||||
"order by alt.name.first desc" +
|
||||
"order by alt.name.first desc " +
|
||||
"limit 1" +
|
||||
") a " +
|
||||
"join a.contact alt",
|
||||
"join a.contact alt " +
|
||||
"order by c.id1",
|
||||
Tuple.class
|
||||
);
|
||||
verifySame(
|
||||
session.createQuery( cq ).getResultList(),
|
||||
query.getResultList(),
|
||||
list -> {
|
||||
assertEquals( 1, list.size() );
|
||||
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
|
||||
assertEquals( 2, list.size() );
|
||||
assertEquals( "John", list.get( 0 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Jane", list.get( 0 ).get( 1, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Jane", list.get( 1 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Granny", list.get( 1 ).get( 1, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -144,7 +150,6 @@ public class SubQueryInFromIdClassTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailureExpected(reason = "Support for id class association selecting in from clause subqueries not yet supported")
|
||||
public void testEntityImplicit(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -162,24 +167,28 @@ public class SubQueryInFromIdClassTests {
|
|||
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.LEFT );
|
||||
|
||||
cq.multiselect( root.get( "name" ), a.get( "contact" ).get( "name" ) );
|
||||
cq.orderBy( cb.asc( root.get( "id1" ) ) );
|
||||
|
||||
final QueryImplementor<Tuple> query = session.createQuery(
|
||||
"select c.name, a.contact.name from Contact c " +
|
||||
"left join lateral (" +
|
||||
"select alt as contact " +
|
||||
"from c.alternativeContact alt " +
|
||||
"order by alt.name.first desc" +
|
||||
"order by alt.name.first desc " +
|
||||
"limit 1" +
|
||||
") a",
|
||||
") a " +
|
||||
"order by c.id1",
|
||||
Tuple.class
|
||||
);
|
||||
verifySame(
|
||||
session.createQuery( cq ).getResultList(),
|
||||
query.getResultList(),
|
||||
list -> {
|
||||
assertEquals( 1, list.size() );
|
||||
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
|
||||
assertEquals( 2, list.size() );
|
||||
assertEquals( "John", list.get( 0 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Jane", list.get( 0 ).get( 1, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Jane", list.get( 1 ).get( 0, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
assertEquals( "Granny", list.get( 1 ).get( 1, SubQueryInFromIdClassTests.Contact.Name.class ).getFirst() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -212,6 +221,7 @@ public class SubQueryInFromIdClassTests {
|
|||
@AfterEach
|
||||
public void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
session.createQuery( "update Contact set alternativeContact = null" ).executeUpdate();
|
||||
session.createQuery( "delete Contact" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ import java.time.LocalDate;
|
|||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.dialect.TiDBDialect;
|
||||
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
||||
import org.hibernate.query.criteria.JpaCriteriaQuery;
|
||||
import org.hibernate.query.criteria.JpaDerivedJoin;
|
||||
|
@ -22,7 +21,6 @@ import org.hibernate.query.sqm.tree.SqmJoinType;
|
|||
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
|
||||
|
||||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.hibernate.testing.orm.domain.StandardDomainModel;
|
||||
import org.hibernate.testing.orm.domain.contacts.Address;
|
||||
import org.hibernate.testing.orm.domain.contacts.Contact;
|
||||
|
@ -84,7 +82,7 @@ public class SubQueryInFromTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
|
||||
public void testBasic(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
|
@ -171,7 +169,7 @@ public class SubQueryInFromTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
|
||||
public void testEmbedded(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
|
@ -254,7 +252,7 @@ public class SubQueryInFromTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
|
||||
public void testEntity(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
|
@ -300,7 +298,7 @@ public class SubQueryInFromTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
|
||||
public void testEntityJoin(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
|
@ -348,7 +346,7 @@ public class SubQueryInFromTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSubqueryInOnClause.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
|
||||
public void testEntityImplicit(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
|
@ -436,6 +434,7 @@ public class SubQueryInFromTests {
|
|||
@AfterEach
|
||||
public void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
session.createQuery( "update Contact set alternativeContact = null" ).executeUpdate();
|
||||
session.createQuery( "delete Contact" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.hibernate.dialect.Dialect;
|
|||
import org.hibernate.dialect.MySQLDialect;
|
||||
import org.hibernate.dialect.NationalizationSupport;
|
||||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
import org.hibernate.dialect.TiDBDialect;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureCheck;
|
||||
|
||||
|
@ -295,4 +296,11 @@ abstract public class DialectChecks {
|
|||
&& !( dialect instanceof AbstractHANADialect );
|
||||
}
|
||||
}
|
||||
|
||||
public static class SupportsSubqueryInOnClause implements DialectCheck {
|
||||
public boolean isMatch(Dialect dialect) {
|
||||
// TiDB db does not support subqueries for ON condition
|
||||
return !( dialect instanceof TiDBDialect );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -448,4 +448,11 @@ abstract public class DialectFeatureChecks {
|
|||
return dialect instanceof PostgreSQLDialect;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SupportsSubqueryInOnClause implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
// TiDB db does not support subqueries for ON condition
|
||||
return !( dialect instanceof TiDBDialect );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue