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