HHH-3356 Support for normal and lateral subquery in from clause

This commit is contained in:
Christian Beikov 2022-06-02 16:09:44 +02:00
parent 4947af946a
commit 341267b133
63 changed files with 4687 additions and 148 deletions

View File

@ -1470,6 +1470,21 @@ This behavior may be slightly adjusted using the `@Polymorphism` annotation.
See <<chapters/domain/inheritance.adoc#entity-inheritance-polymorphism>> for more.
[[hql-derived-root]]
==== Derived root
As of Hibernate 6.1, HQL allows to declare derived roots, based on a sub query in the `from` clause.
[[hql-derived-root-example]]
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-derived-root-example, indent=0]
----
====
This can be used to split up more complicated queries into smaller parts.
[[hql-join]]
=== Declaring joined entities
@ -1626,6 +1641,31 @@ This allows the attribute `cardNumber` declared by the subtype `CreditCardPaymen
See <<hql-treat-type>> for more information about `treat()`.
[[hql-join-derived]]
==== Join subquery
As of Hibernate 6.1, HQL allows against to join sub queries.
[[hql-derived-join-example]]
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-derived-join-example, indent=0]
----
====
This is very similar to defining a <<hql-derived-root,derived root>>, but the particular interesting part here,
is the use of the `lateral` keyword, which allows to refer to previous from clause nodes within the subquery.
This is particularly useful for computing top-N elements of multiple groups.
[NOTE]
====
Most databases support lateral natively, but for certain databases it is necessary to emulate this feature.
Beware that the emulation is neither very efficient, nor does it support all possible query shapes,
so be sure to test such queries against your desired target database.
====
[[hql-implicit-join]]
==== Implicit joins (path expressions)

View File

@ -13,3 +13,20 @@ query
queryOrder
: orderByClause limitClause? offsetClause? fetchClause?
fromClause
: FROM entityWithJoins (COMMA entityWithJoins)*
entityWithJoins
: fromRoot (join | crossJoin | jpaCollectionJoin)*
fromRoot
: entityName variable?
| LATERAL? LEFT_PAREN subquery RIGHT_PAREN variable?
join
: joinType JOIN FETCH? joinTarget joinRestriction?
joinTarget
: path variable?
| LATERAL? LEFT_PAREN subquery RIGHT_PAREN variable?

View File

@ -51,6 +51,7 @@ import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.junit.Before;
import org.junit.Test;
@ -3043,4 +3044,47 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
//end::hql-read-only-entities-native-example[]
});
}
@Test
public void test_hql_derived_root_example() {
doInJPA(this::entityManagerFactory, entityManager -> {
//tag::hql-derived-root-example[]
List<Tuple> calls = entityManager.createQuery(
"select d.owner, d.payed " +
"from (" +
" select p.person as owner, c.payment is not null as payed " +
" from Call c " +
" join c.phone p " +
" where p.number = :phoneNumber) d",
Tuple.class)
.setParameter("phoneNumber", "123-456-7890")
.getResultList();
//end::hql-derived-root-example[]
});
}
@Test
@SkipForDialect(value = TiDBDialect.class, comment = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(DialectChecks.SupportsOrderByInCorrelatedSubquery.class)
public void test_hql_derived_join_example() {
doInJPA(this::entityManagerFactory, entityManager -> {
//tag::hql-derived-join-example[]
List<Tuple> calls = entityManager.createQuery(
"select longest.duration " +
"from Phone p " +
"left join lateral (" +
" select c.duration as duration " +
" from p.calls c" +
" order by c.duration desc" +
" limit 1 " +
" ) longest " +
"where p.number = :phoneNumber",
Tuple.class)
.setParameter("phoneNumber", "123-456-7890")
.getResultList();
//end::hql-derived-join-example[]
});
}
}

View File

@ -207,6 +207,7 @@ IS : [iI] [sS];
JOIN : [jJ] [oO] [iI] [nN];
KEY : [kK] [eE] [yY];
LAST : [lL] [aA] [sS] [tT];
LATERAL : [lL] [aA] [tT] [eE] [rR] [aA] [lL];
LEADING : [lL] [eE] [aA] [dD] [iI] [nN] [gG];
LEFT : [lL] [eE] [fF] [tT];
LIKE : [lL] [iI] [kK] [eE];

View File

@ -169,14 +169,15 @@ fromClause
* The declaration of a root entity in 'from' clause, along with its joins
*/
entityWithJoins
: rootEntity (join | crossJoin | jpaCollectionJoin)*
: fromRoot (join | crossJoin | jpaCollectionJoin)*
;
/**
* A root entity declaration in the 'from' clause, with optional identification variable
*/
rootEntity
: entityName variable?
fromRoot
: entityName variable? # RootEntity
| LATERAL? LEFT_PAREN subquery RIGHT_PAREN variable? # RootSubquery
;
/**
@ -212,7 +213,7 @@ jpaCollectionJoin
* A 'join', with an optional 'on' or 'with' clause
*/
join
: joinType JOIN FETCH? joinPath joinRestriction?
: joinType JOIN FETCH? joinTarget joinRestriction?
;
/**
@ -226,8 +227,9 @@ joinType
/**
* The joined path, with an optional identification variable
*/
joinPath
: path variable?
joinTarget
: path variable? #JoinPath
| LATERAL? LEFT_PAREN subquery RIGHT_PAREN variable? #JoinSubquery
;
/**

View File

@ -1081,7 +1081,8 @@ public abstract class AbstractHANADialect extends Dialect {
@Override
public boolean supportsOrderByInSubquery() {
return false;
// Seems to work, though I don't know as of which version
return true;
}
@Override

View File

@ -577,7 +577,8 @@ public class DerbyDialect extends Dialect {
@Override
public boolean supportsOrderByInSubquery() {
return false;
// As of version 10.5 Derby supports OFFSET and FETCH as well as ORDER BY in subqueries
return getVersion().isSameOrAfter( 10, 5 );
}
@Override

View File

@ -31,6 +31,8 @@ import org.hibernate.sql.exec.spi.JdbcOperation;
*/
public class HANASqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
private boolean inLateral;
public HANASqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
super( sessionFactory, statement );
}
@ -64,7 +66,20 @@ public class HANASqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
@Override
public void visitQueryPartTableReference(QueryPartTableReference tableReference) {
if ( tableReference.isLateral() && !inLateral ) {
inLateral = true;
emulateQueryPartTableReferenceColumnAliasing( tableReference );
inLateral = false;
}
else {
emulateQueryPartTableReferenceColumnAliasing( tableReference );
}
}
@Override
protected SqlAstNodeRenderingMode getParameterRenderingMode() {
// HANA does not support parameters in lateral sub queries for some reason, so inline all the parameters in this case
return inLateral ? SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS : super.getParameterRenderingMode();
}
@Override

View File

@ -10,6 +10,8 @@ import java.util.List;
import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
@ -213,10 +215,15 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
}
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType();
if ( lhsExpressionType == null ) {
renderComparisonStandard( lhs, operator, rhs );
return;
}
switch ( operator ) {
case DISTINCT_FROM:
case NOT_DISTINCT_FROM:
if ( lhs.getExpressionType().getJdbcMappings().get( 0 ).getJdbcType() instanceof ArrayJdbcType ) {
if ( lhsExpressionType.getJdbcMappings().get( 0 ).getJdbcType() instanceof ArrayJdbcType ) {
// HSQL implements distinct from semantics for arrays
lhs.accept( this );
appendSql( operator == ComparisonOperator.DISTINCT_FROM ? "<>" : "=" );

View File

@ -132,6 +132,17 @@ public class MariaDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
return false;
}
@Override
protected boolean supportsIntersect() {
return getDialect().getVersion().isSameOrAfter( 10, 3 );
}
@Override
protected boolean supportsDistinctFromPredicate() {
// It supports a proprietary operator
return true;
}
private boolean supportsWindowFunctions() {
return getDialect().getVersion().isSameOrAfter( 10, 2 );
}

View File

@ -149,6 +149,17 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
return false;
}
@Override
protected boolean supportsIntersect() {
return false;
}
@Override
protected boolean supportsDistinctFromPredicate() {
// It supports a proprietary operator
return true;
}
@Override
protected String getFromDual() {
return " from dual";

View File

@ -11,6 +11,7 @@ import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
@ -354,12 +355,12 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
if ( lhs.getExpressionType() == null ) {
final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType();
if ( lhsExpressionType == null ) {
renderComparisonEmulateDecode( lhs, operator, rhs );
return;
}
final JdbcMapping lhsMapping = lhs.getExpressionType().getJdbcMappings().get( 0 );
switch ( lhsMapping.getJdbcType().getJdbcTypeCode() ) {
switch ( lhsExpressionType.getJdbcMappings().get( 0 ).getJdbcType().getJdbcTypeCode() ) {
case SqlTypes.SQLXML:
// In Oracle, XMLTYPE is not "comparable", so we have to use the xmldiff function for this purpose
switch ( operator ) {

View File

@ -15,6 +15,8 @@ import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
@ -76,6 +78,16 @@ public class TiDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
}
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
emulateValuesTableReferenceColumnAliasing( tableReference );
}
@Override
public void visitQueryPartTableReference(QueryPartTableReference tableReference) {
emulateQueryPartTableReferenceColumnAliasing( tableReference );
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
if ( !isRowNumberingCurrentQueryPart() ) {

View File

@ -359,7 +359,26 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
TableReference keySideReference,
SqlExpressionResolver sqlExpressionResolver,
SqlAstCreationContext creationContext) {
return getPredicate( targetSideReference, keySideReference, creationContext, targetSelectableMappings, keySelectableMappings );
final Junction predicate = new Junction( Junction.Nature.CONJUNCTION );
targetSelectableMappings.forEachSelectable(
(i, selection) -> {
final ComparisonPredicate comparisonPredicate = new ComparisonPredicate(
new ColumnReference(
targetSideReference,
selection,
creationContext.getSessionFactory()
),
ComparisonOperator.EQUAL,
new ColumnReference(
keySideReference,
keySelectableMappings.getSelectable( i ),
creationContext.getSessionFactory()
)
);
predicate.add( comparisonPredicate );
}
);
return predicate;
}
@Override
@ -440,34 +459,6 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
return true;
}
private Predicate getPredicate(
TableReference lhs,
TableReference rhs,
SqlAstCreationContext creationContext,
SelectableMappings lhsMappings,
SelectableMappings rhsMappings) {
final Junction predicate = new Junction( Junction.Nature.CONJUNCTION );
lhsMappings.forEachSelectable(
(i, selection) -> {
final ComparisonPredicate comparisonPredicate = new ComparisonPredicate(
new ColumnReference(
lhs,
selection,
creationContext.getSessionFactory()
),
ComparisonOperator.EQUAL,
new ColumnReference(
rhs,
rhsMappings.getSelectable( i ),
creationContext.getSessionFactory()
)
);
predicate.add( comparisonPredicate );
}
);
return predicate;
}
protected TableReference getTableReference(TableGroup lhs, TableGroup tableGroup, String table) {
TableReference tableReference = lhs.getPrimaryTableReference().resolveTableReference( table );
if ( tableReference != null ) {

View File

@ -642,6 +642,10 @@ public class ToOneAttributeMapping
&& declaringTableGroupProducer.containsTableReference( identifyingColumnsTableExpression );
}
public String getIdentifyingColumnsTableExpression() {
return identifyingColumnsTableExpression;
}
public void setIdentifyingColumnsTableExpression(String tableExpression) {
identifyingColumnsTableExpression = tableExpression;
}
@ -656,6 +660,10 @@ public class ToOneAttributeMapping
return sideNature;
}
public boolean isReferenceToPrimaryKey() {
return foreignKeyDescriptor.getSide( sideNature.inverse() ).getModelPart() instanceof EntityIdentifierMapping;
}
public String getReferencedPropertyName() {
return referencedPropertyName;
}

View File

@ -8,7 +8,11 @@ package org.hibernate.metamodel.model.domain;
import java.util.List;
import org.hibernate.Incubating;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.TableGroupProducer;
/**
* Describes any structural type without a direct java type representation.
@ -23,4 +27,5 @@ public interface TupleType<J> extends SqmExpressible<J> {
SqmExpressible<?> get(int index);
SqmExpressible<?> get(String componentName);
}

View File

@ -0,0 +1,29 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.criteria;
import org.hibernate.Incubating;
/**
* @author Christian Beikov
*/
@Incubating
public interface JpaDerivedFrom<T> extends JpaFrom<T,T> {
/**
* The sub query part for this derived from node.
*/
JpaSubQuery<T> getQueryPart();
/**
* Specifies whether the sub query part can access previous from node aliases.
* Normally, sub queries in the from clause are unable to access other from nodes,
* but when specifying them as lateral, they are allowed to do so.
* Refer to the SQL standard definition of LATERAL for more details.
*/
boolean isLateral();
}

View File

@ -0,0 +1,18 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.criteria;
import org.hibernate.Incubating;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
/**
* @author Christian Beikov
*/
@Incubating
public interface JpaDerivedJoin<T> extends JpaDerivedFrom<T>, SqmQualifiedJoin<T,T>, JpaJoinedFrom<T,T> {
}

View File

@ -0,0 +1,17 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.criteria;
import org.hibernate.Incubating;
/**
* @author Christian Beikov
*/
@Incubating
public interface JpaDerivedRoot<T> extends JpaDerivedFrom<T>, JpaRoot<T> {
}

View File

@ -6,10 +6,12 @@
*/
package org.hibernate.query.criteria;
import org.hibernate.Incubating;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.sqm.tree.SqmJoinType;
import jakarta.persistence.criteria.From;
import jakarta.persistence.criteria.Subquery;
/**
* API extension to the JPA {@link From} contract
@ -27,4 +29,20 @@ public interface JpaFrom<O,T> extends JpaPath<T>, JpaFetchParent<O,T>, From<O,T>
<X> JpaEntityJoin<X> join(Class<X> entityJavaType, SqmJoinType joinType);
<X> JpaEntityJoin<X> join(EntityDomainType<X> entity, SqmJoinType joinType);
@Incubating
<X> JpaDerivedJoin<X> join(Subquery<X> subquery);
@Incubating
<X> JpaDerivedJoin<X> join(Subquery<X> subquery, SqmJoinType joinType);
@Incubating
<X> JpaDerivedJoin<X> joinLateral(Subquery<X> subquery);
@Incubating
<X> JpaDerivedJoin<X> joinLateral(Subquery<X> subquery, SqmJoinType joinType);
@Incubating
<X> JpaDerivedJoin<X> join(Subquery<X> subquery, SqmJoinType joinType, boolean lateral);
}

View File

@ -17,5 +17,6 @@ public interface JpaRoot<T> extends JpaFrom<T,T>, Root<T> {
@Override
EntityDomainType<T> getModel();
// todo: deprecate and remove?
EntityDomainType<T> getManagedType();
}

View File

@ -10,6 +10,7 @@ import java.util.List;
import jakarta.persistence.criteria.AbstractQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.EntityType;
/**
@ -28,6 +29,35 @@ public interface JpaSelectCriteria<T> extends AbstractQuery<T>, JpaCriteriaBase
*/
JpaQueryPart<T> getQueryPart();
/**
* Create and add a query root corresponding to the given sub query,
* forming a cartesian product with any existing roots.
*
* @param subquery the sub query
* @return query root corresponding to the given sub query
*/
<X> JpaDerivedRoot<X> from(Subquery<X> subquery);
/**
* Create and add a query root corresponding to the given lateral sub query,
* forming a cartesian product with any existing roots.
*
* @param subquery the sub query
* @return query root corresponding to the given sub query
*/
<X> JpaDerivedRoot<X> fromLateral(Subquery<X> subquery);
/**
* Create and add a query root corresponding to the given sub query,
* forming a cartesian product with any existing roots.
* If the sub query is marked as lateral, it may access previous from elements.
*
* @param subquery the sub query
* @param lateral whether to allow access to previous from elements in the sub query
* @return query root corresponding to the given sub query
*/
<X> JpaDerivedRoot<X> from(Subquery<X> subquery, boolean lateral);
@Override
JpaSelectCriteria<T> distinct(boolean distinct);

View File

@ -9,12 +9,12 @@ package org.hibernate.query.criteria;
import java.util.List;
import java.util.Set;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.SetJoin;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.criteria.Subquery;
import org.hibernate.query.sqm.tree.domain.SqmSetJoin;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmJoin;
@ -24,12 +24,49 @@ import org.hibernate.query.sqm.tree.from.SqmJoin;
*/
public interface JpaSubQuery<T> extends Subquery<T>, JpaSelectCriteria<T>, JpaExpression<T> {
JpaSubQuery<T> multiselect(Selection<?>... selections);
JpaSubQuery<T> multiselect(List<Selection<?>> selectionList);
<X> SqmCrossJoin<X> correlate(SqmCrossJoin<X> parentCrossJoin);
<X> SqmEntityJoin<X> correlate(SqmEntityJoin<X> parentEntityJoin);
Set<SqmJoin<?, ?>> getCorrelatedSqmJoins();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Limit/Offset/Fetch clause
JpaExpression<Number> getOffset();
JpaSubQuery<T> offset(JpaExpression<? extends Number> offset);
JpaSubQuery<T> offset(Number offset);
JpaExpression<Number> getFetch();
JpaSubQuery<T> fetch(JpaExpression<? extends Number> fetch);
JpaSubQuery<T> fetch(JpaExpression<? extends Number> fetch, FetchClauseType fetchClauseType);
JpaSubQuery<T> fetch(Number fetch);
JpaSubQuery<T> fetch(Number fetch, FetchClauseType fetchClauseType);
FetchClauseType getFetchClauseType();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Order by clause
List<JpaOrder> getOrderList();
JpaSubQuery<T> orderBy(Order... o);
JpaSubQuery<T> orderBy(List<Order> o);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Covariant overrides
@Override
JpaSubQuery<T> distinct(boolean distinct);

View File

@ -0,0 +1,69 @@
/*
* 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 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.JdbcMapping;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.query.sqm.SqmExpressible;
/**
* @author Christian Beikov
*/
@Incubating
public class AnonymousTupleBasicEntityIdentifierMapping extends AnonymousTupleBasicValuedModelPart
implements BasicEntityIdentifierMapping {
private final BasicEntityIdentifierMapping delegate;
public AnonymousTupleBasicEntityIdentifierMapping(
String selectionExpression,
SqmExpressible<?> expressible,
JdbcMapping jdbcMapping,
BasicEntityIdentifierMapping delegate) {
super( delegate.getAttributeName(), selectionExpression, expressible, jdbcMapping );
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 delegate.getPropertyAccess();
}
@Override
public String getAttributeName() {
return getPartName();
}
}

View File

@ -0,0 +1,302 @@
/*
* 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.function.BiConsumer;
import org.hibernate.Incubating;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.FetchOptions;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.basic.BasicFetch;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.type.descriptor.java.JavaType;
import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey;
/**
* @author Christian Beikov
*/
@Incubating
public class AnonymousTupleBasicValuedModelPart implements ModelPart, MappingType, BasicValuedModelPart {
private static final FetchOptions FETCH_OPTIONS = FetchOptions.valueOf( FetchTiming.IMMEDIATE, FetchStyle.JOIN );
private final String partName;
private final String selectionExpression;
private final SqmExpressible<?> expressible;
private final JdbcMapping jdbcMapping;
public AnonymousTupleBasicValuedModelPart(
String partName,
String selectionExpression,
SqmExpressible<?> expressible,
JdbcMapping jdbcMapping) {
this.partName = partName;
this.selectionExpression = selectionExpression;
this.expressible = expressible;
this.jdbcMapping = jdbcMapping;
}
@Override
public MappingType getPartMappingType() {
return this;
}
@Override
public JavaType<?> getJavaType() {
return expressible.getExpressibleJavaType();
}
@Override
public JavaType<?> getMappedJavaType() {
return expressible.getExpressibleJavaType();
}
@Override
public String getPartName() {
return partName;
}
@Override
public NavigableRole getNavigableRole() {
return null;
}
@Override
public EntityMappingType findContainingEntityMapping() {
return null;
}
@Override
public JdbcMapping getJdbcMapping() {
return jdbcMapping;
}
@Override
public String getContainingTableExpression() {
return "";
}
@Override
public String getSelectionExpression() {
return selectionExpression;
}
@Override
public String getCustomReadExpression() {
return null;
}
@Override
public String getCustomWriteExpression() {
return null;
}
@Override
public boolean isFormula() {
return false;
}
@Override
public String getColumnDefinition() {
return null;
}
@Override
public Long getLength() {
return null;
}
@Override
public Integer getPrecision() {
return null;
}
@Override
public Integer getScale() {
return null;
}
@Override
public MappingType getMappedType() {
return this;
}
@Override
public String getFetchableName() {
return partName;
}
@Override
public FetchOptions getMappedFetchOptions() {
return FETCH_OPTIONS;
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
final SqlSelection sqlSelection = resolveSqlSelection(
navigablePath,
tableGroup,
null,
creationState.getSqlAstCreationState()
);
//noinspection unchecked
return new BasicResult(
sqlSelection.getValuesArrayPosition(),
resultVariable,
getJavaType(),
null,
navigablePath
);
}
private SqlSelection resolveSqlSelection(
NavigablePath navigablePath,
TableGroup tableGroup,
FetchParent fetchParent,
SqlAstCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlExpressionResolver();
final Expression expression = expressionResolver.resolveSqlExpression(
createColumnReferenceKey( tableGroup.getPrimaryTableReference(), getSelectionExpression() ),
sqlAstProcessingState -> new ColumnReference(
tableGroup.resolveTableReference( navigablePath, "" ),
this,
creationState.getCreationContext().getSessionFactory()
)
);
return expressionResolver.resolveSqlSelection(
expression,
getJdbcMapping().getJavaTypeDescriptor(),
fetchParent,
creationState.getCreationContext().getSessionFactory().getTypeConfiguration()
);
}
@Override
public BasicFetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
String resultVariable,
DomainResultCreationState creationState) {
final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState();
final TableGroup tableGroup = sqlAstCreationState.getFromClauseAccess().getTableGroup(
fetchParent.getNavigablePath()
);
assert tableGroup != null;
final SqlSelection sqlSelection = resolveSqlSelection(
fetchablePath,
tableGroup,
fetchParent,
creationState.getSqlAstCreationState()
);
return new BasicFetch<>(
sqlSelection.getValuesArrayPosition(),
fetchParent,
fetchablePath,
this,
null,
fetchTiming,
creationState
);
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState) {
resolveSqlSelection( navigablePath, tableGroup, null, creationState.getSqlAstCreationState() );
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState,
BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
selectionConsumer.accept(
resolveSqlSelection( navigablePath, tableGroup, null, creationState.getSqlAstCreationState() ),
getJdbcMapping()
);
}
@Override
public int forEachDisassembledJdbcValue(
Object value,
Clause clause,
int offset,
JdbcValuesConsumer valuesConsumer,
SharedSessionContractImplementor session) {
valuesConsumer.consume( offset, value, getJdbcMapping() );
return getJdbcTypeCount();
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
action.accept( offset, getJdbcMapping() );
return getJdbcTypeCount();
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, this );
}
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
return value;
}
@Override
public int forEachJdbcValue(Object value, Clause clause, int offset, JdbcValuesConsumer valuesConsumer, SharedSessionContractImplementor session) {
valuesConsumer.consume( offset, value, getJdbcMapping() );
return getJdbcTypeCount();
}
@Override
public int forEachSelectable(int offset, SelectableConsumer consumer) {
consumer.accept( offset, this );
return getJdbcTypeCount();
}
@Override
public int forEachJdbcType(IndexedConsumer<JdbcMapping> action) {
action.accept( 0, getJdbcMapping() );
return getJdbcTypeCount();
}
}

View File

@ -0,0 +1,472 @@
/*
* 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.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.hibernate.Incubating;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SelectableMappings;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.StandardVirtualTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupProducer;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchOptions;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
import org.hibernate.type.descriptor.java.JavaType;
/**
* @author Christian Beikov
*/
@Incubating
public class AnonymousTupleEmbeddableValuedModelPart implements EmbeddableValuedModelPart, EmbeddableMappingType {
private static final FetchOptions FETCH_OPTIONS = FetchOptions.valueOf( FetchTiming.IMMEDIATE, FetchStyle.JOIN );
private final Map<String, ModelPart> modelParts;
private final DomainType<?> domainType;
private final String componentName;
private final EmbeddableValuedModelPart existingModelPartContainer;
public AnonymousTupleEmbeddableValuedModelPart(
Map<String, ModelPart> modelParts,
DomainType<?> domainType,
String componentName,
EmbeddableValuedModelPart existingModelPartContainer) {
this.modelParts = modelParts;
this.domainType = domainType;
this.componentName = componentName;
this.existingModelPartContainer = existingModelPartContainer;
}
@Override
public ModelPart findSubPart(String name, EntityMappingType treatTargetType) {
return modelParts.get( name );
}
@Override
public void visitSubParts(Consumer<ModelPart> consumer, EntityMappingType treatTargetType) {
modelParts.values().forEach( consumer );
}
@Override
public MappingType getPartMappingType() {
return this;
}
@Override
public JavaType<?> getJavaType() {
return domainType.getExpressibleJavaType();
}
@Override
public String getPartName() {
return componentName;
}
@Override
public int getJdbcTypeCount() {
return existingModelPartContainer.getJdbcTypeCount();
}
@Override
public EmbeddableMappingType getEmbeddableTypeDescriptor() {
return this;
}
@Override
public EmbeddableValuedModelPart getEmbeddedValueMapping() {
return this;
}
@Override
public EmbeddableRepresentationStrategy getRepresentationStrategy() {
return existingModelPartContainer.getEmbeddableTypeDescriptor()
.getRepresentationStrategy();
}
@Override
public boolean isCreateEmptyCompositesEnabled() {
return false;
}
@Override
public EmbeddableMappingType createInverseMappingType(
EmbeddedAttributeMapping valueMapping,
TableGroupProducer declaringTableGroupProducer,
SelectableMappings selectableMappings,
MappingModelCreationProcess creationProcess) {
throw new UnsupportedOperationException();
}
@Override
public int getNumberOfAttributeMappings() {
return modelParts.size();
}
@Override
public AttributeMapping getAttributeMapping(int position) {
throw new UnsupportedOperationException();
}
@Override
public List<AttributeMapping> getAttributeMappings() {
throw new UnsupportedOperationException();
}
@Override
public void visitAttributeMappings(Consumer<? super AttributeMapping> action) {
throw new UnsupportedOperationException();
}
@Override
public Object[] getValues(Object instance) {
return existingModelPartContainer.getEmbeddableTypeDescriptor()
.getValues( instance );
}
@Override
public Object getValue(Object instance, int position) {
return existingModelPartContainer.getEmbeddableTypeDescriptor()
.getAttributeMapping( position )
.getValue( instance );
}
@Override
public void setValues(Object instance, Object[] resolvedValues) {
existingModelPartContainer.getEmbeddableTypeDescriptor()
.setValues( instance, resolvedValues );
}
@Override
public void setValue(Object instance, int position, Object value) {
existingModelPartContainer.getEmbeddableTypeDescriptor()
.getAttributeMapping( position )
.setValue( instance, value );
}
@Override
public SelectableMapping getSelectable(int columnIndex) {
final List<SelectableMapping> results = new ArrayList<>();
forEachSelectable( (index, selection) -> results.add( selection ) );
return results.get( columnIndex );
}
@Override
public List<JdbcMapping> getJdbcMappings() {
final List<JdbcMapping> results = new ArrayList<>();
forEachSelectable( (index, selection) -> results.add( selection.getJdbcMapping() ) );
return results;
}
@Override
public int forEachSelectable(SelectableConsumer consumer) {
return forEachSelectable( 0, consumer );
}
@Override
public int forEachSelectable(int offset, SelectableConsumer consumer) {
int span = 0;
for ( ModelPart mapping : modelParts.values() ) {
span += mapping.forEachSelectable( offset + span, consumer );
}
return span;
}
@Override
public String getContainingTableExpression() {
return "";
}
@Override
public SqlTuple toSqlExpression(
TableGroup tableGroup,
Clause clause,
SqmToSqlAstConverter walker,
SqlAstCreationState sqlAstCreationState) {
final List<ColumnReference> columnReferences = CollectionHelper.arrayList( getJdbcTypeCount() );
final NavigablePath navigablePath = tableGroup.getNavigablePath().append( componentName );
final TableReference tableReference = tableGroup.resolveTableReference( navigablePath, getContainingTableExpression() );
for ( ModelPart modelPart : modelParts.values() ) {
modelPart.forEachSelectable(
(columnIndex, selection) -> {
final Expression columnReference = sqlAstCreationState.getSqlExpressionResolver()
.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableReference,
selection.getSelectionExpression()
),
sqlAstProcessingState -> new ColumnReference(
tableReference.getIdentificationVariable(),
selection,
sqlAstCreationState.getCreationContext().getSessionFactory()
)
);
columnReferences.add( columnReference.getColumnReference() );
}
);
}
return new SqlTuple( columnReferences, this );
}
@Override
public JavaType<?> getMappedJavaType() {
return existingModelPartContainer.getJavaType();
}
@Override
public SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup) {
return SqlAstJoinType.INNER;
}
@Override
public boolean isSimpleJoinPredicate(Predicate predicate) {
return predicate == null;
}
@Override
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final SqlAstJoinType joinType = requestedJoinType == null ? SqlAstJoinType.INNER : requestedJoinType;
final TableGroup tableGroup = createRootTableGroupJoin(
navigablePath,
lhs,
explicitSourceAlias,
requestedJoinType,
fetched,
null,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
return new TableGroupJoin( navigablePath, joinType, tableGroup );
}
@Override
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
return new StandardVirtualTableGroup(
navigablePath,
this,
lhs,
fetched
);
}
@Override
public String getSqlAliasStem() {
return getPartName();
}
@Override
public String getFetchableName() {
return getPartName();
}
@Override
public FetchOptions getMappedFetchOptions() {
return FETCH_OPTIONS;
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
String resultVariable,
DomainResultCreationState creationState) {
throw new UnsupportedOperationException( "AnonymousTupleEmbeddableValuedModelPart is not fetchable!" );
}
@Override
public int getNumberOfFetchables() {
return modelParts.size();
}
@Override
public NavigableRole getNavigableRole() {
return null;
}
@Override
public EntityMappingType findContainingEntityMapping() {
return null;
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
return new EmbeddableResultImpl<>(
navigablePath,
this,
resultVariable,
creationState
);
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState) {
for ( ModelPart mapping : modelParts.values() ) {
mapping.applySqlSelections( navigablePath, tableGroup, creationState );
}
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState,
BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
for ( ModelPart mapping : modelParts.values() ) {
mapping.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer );
}
}
@Override
public void breakDownJdbcValues(
Object domainValue,
JdbcValueConsumer valueConsumer,
SharedSessionContractImplementor session) {
final Object[] values = (Object[]) domainValue;
assert values.length == modelParts.size();
int i = 0;
for ( ModelPart mapping : modelParts.values() ) {
final Object attributeValue = values[ i ];
mapping.breakDownJdbcValues( attributeValue, valueConsumer, session );
i++;
}
}
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
final Object[] values = (Object[]) value;
final Object[] result = new Object[ modelParts.size() ];
int i = 0;
for ( ModelPart mapping : modelParts.values() ) {
Object o = values[i];
result[i] = mapping.disassemble( o, session );
i++;
}
return result;
}
@Override
public int forEachDisassembledJdbcValue(
Object value,
Clause clause,
int offset,
JdbcValuesConsumer valuesConsumer,
SharedSessionContractImplementor session) {
final Object[] values = (Object[]) value;
int span = 0;
int i = 0;
for ( ModelPart mapping : modelParts.values() ) {
span += mapping.forEachDisassembledJdbcValue( values[i], clause, span + offset, valuesConsumer, session );
i++;
}
return span;
}
@Override
public int forEachJdbcValue(
Object value,
Clause clause,
int offset,
JdbcValuesConsumer consumer,
SharedSessionContractImplementor session) {
final Object[] values = (Object[]) value;
int span = 0;
int i = 0;
for ( ModelPart attributeMapping : modelParts.values() ) {
final Object o = values[i];
span += attributeMapping.forEachJdbcValue( o, clause, span + offset, consumer, session );
i++;
}
return span;
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
int span = 0;
for ( ModelPart attributeMapping : modelParts.values() ) {
span += attributeMapping.forEachJdbcType( span + offset, action );
}
return span;
}
}

View File

@ -0,0 +1,505 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.hibernate.Incubating;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.type.descriptor.java.JavaType;
import static java.util.Objects.requireNonNullElse;
/**
* @author Christian Beikov
*/
@Incubating
public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPart, EntityMappingType,
TableGroupJoinProducer {
private final EntityIdentifierMapping identifierMapping;
private final DomainType<?> domainType;
private final String componentName;
private final EntityValuedModelPart delegate;
public AnonymousTupleEntityValuedModelPart(
EntityIdentifierMapping identifierMapping,
DomainType<?> domainType,
String componentName,
EntityValuedModelPart delegate) {
this.identifierMapping = identifierMapping;
this.domainType = domainType;
this.componentName = componentName;
this.delegate = delegate;
}
@Override
public ModelPart findSubPart(String name, EntityMappingType treatTargetType) {
if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) {
if ( ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName().equals( name ) ) {
return identifierMapping;
}
}
else {
final ModelPart subPart = ( (CompositeIdentifierMapping) identifierMapping ).getPartMappingType().findSubPart(
name,
treatTargetType
);
if ( subPart != null ) {
return subPart;
}
}
return delegate.findSubPart( name, treatTargetType );
}
@Override
public void visitSubParts(Consumer<ModelPart> consumer, EntityMappingType treatTargetType) {
delegate.visitSubParts( consumer, treatTargetType );
}
@Override
public MappingType getPartMappingType() {
return this;
}
@Override
public JavaType<?> getJavaType() {
return domainType.getExpressibleJavaType();
}
@Override
public String getPartName() {
return componentName;
}
@Override
public int getJdbcTypeCount() {
return delegate.getJdbcTypeCount();
}
@Override
public int getNumberOfAttributeMappings() {
return delegate.getEntityMappingType().getNumberOfAttributeMappings();
}
@Override
public AttributeMapping getAttributeMapping(int position) {
return delegate.getEntityMappingType().getAttributeMapping( position );
}
@Override
public List<AttributeMapping> getAttributeMappings() {
return delegate.getEntityMappingType().getAttributeMappings();
}
@Override
public void visitAttributeMappings(Consumer<? super AttributeMapping> action) {
delegate.getEntityMappingType().visitAttributeMappings( action );
}
@Override
public Object[] getValues(Object instance) {
return delegate.getEntityMappingType().getValues( instance );
}
@Override
public Object getValue(Object instance, int position) {
return delegate.getEntityMappingType()
.getAttributeMapping( position )
.getValue( instance );
}
@Override
public void setValues(Object instance, Object[] resolvedValues) {
delegate.getEntityMappingType().setValues( instance, resolvedValues );
}
@Override
public void setValue(Object instance, int position, Object value) {
delegate.getEntityMappingType()
.getAttributeMapping( position )
.setValue( instance, value );
}
@Override
public List<JdbcMapping> getJdbcMappings() {
final List<JdbcMapping> results = new ArrayList<>();
forEachSelectable( (index, selection) -> results.add( selection.getJdbcMapping() ) );
return results;
}
@Override
public int forEachSelectable(SelectableConsumer consumer) {
return forEachSelectable( 0, consumer );
}
@Override
public int forEachSelectable(int offset, SelectableConsumer consumer) {
return identifierMapping.forEachSelectable( offset, consumer );
}
@Override
public JavaType<?> getMappedJavaType() {
return delegate.getJavaType();
}
@Override
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAstJoinType requestedJoinType,
boolean fetched,
boolean addsPredicate,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
// Need to create a root table group and join predicate separately instead of a table group join directly,
// because the column names on the "key-side" have different names
final TableGroup tableGroup = ( (TableGroupJoinProducer) delegate ).createRootTableGroupJoin(
navigablePath,
lhs,
explicitSourceAlias,
joinType,
fetched,
null,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
tableGroup.getNavigablePath(),
joinType,
tableGroup,
null
);
final List<SelectableMapping> keyMappings;
final List<SelectableMapping> targetMappings;
if ( delegate instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) this.delegate;
final ModelPart targetJoinModelPart = toOneAttributeMapping.getForeignKeyDescriptor()
.getPart( toOneAttributeMapping.getSideNature().inverse() );
targetMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() );
targetJoinModelPart.forEachSelectable(
0,
(i, selectableMapping) -> targetMappings.add( selectableMapping )
);
keyMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() );
toOneAttributeMapping.getForeignKeyDescriptor()
.getPart( toOneAttributeMapping.getSideNature() )
.forEachSelectable(
0,
(i, selectableMapping) -> keyMappings.add( selectableMapping )
);
}
else {
final ModelPart targetJoinModelPart = delegate.getEntityMappingType().getIdentifierMapping();
targetMappings = new ArrayList<>( targetJoinModelPart.getJdbcTypeCount() );
targetJoinModelPart.forEachSelectable(
0,
(i, selectableMapping) -> targetMappings.add( selectableMapping )
);
keyMappings = targetMappings;
}
final TableReference tableReference = lhs.getPrimaryTableReference();
this.identifierMapping.forEachSelectable(
(i, selectableMapping) -> {
final SelectableMapping targetMapping = targetMappings.get( i );
final TableReference targetTableReference = tableGroup.resolveTableReference(
null,
targetMapping.getContainingTableExpression(),
false
);
tableGroupJoin.applyPredicate(
new ComparisonPredicate(
// It is important to resolve the sql expression here,
// as this selectableMapping is the "derived" one.
// We want to register the expression under the key of the original mapping
// which leads to this expression being used for a possible domain result
sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableReference,
keyMappings.get( i ).getSelectionExpression()
),
state -> new ColumnReference(
tableReference,
selectableMapping,
sessionFactory
)
),
ComparisonOperator.EQUAL,
new ColumnReference(
targetTableReference,
targetMapping,
sessionFactory
)
)
);
}
);
return tableGroupJoin;
}
@Override
public TableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
boolean fetched,
Consumer<Predicate> predicateConsumer,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
return ( (TableGroupJoinProducer) delegate ).createRootTableGroupJoin(
navigablePath,
lhs,
explicitSourceAlias,
sqlAstJoinType,
fetched,
predicateConsumer,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
}
@Override
public String getSqlAliasStem() {
return getPartName();
}
@Override
public int getNumberOfFetchables() {
return delegate.getNumberOfFetchables();
}
@Override
public NavigableRole getNavigableRole() {
return delegate.getNavigableRole();
}
@Override
public EntityMappingType findContainingEntityMapping() {
return this;
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
return delegate.createDomainResult( navigablePath, tableGroup, resultVariable, creationState );
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState) {
identifierMapping.applySqlSelections( navigablePath, tableGroup, creationState );
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState,
BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
identifierMapping.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer );
}
@Override
public void breakDownJdbcValues(
Object domainValue,
JdbcValueConsumer valueConsumer,
SharedSessionContractImplementor session) {
delegate.breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
return delegate.disassemble( value, session );
}
@Override
public int forEachDisassembledJdbcValue(
Object value,
Clause clause,
int offset,
JdbcValuesConsumer valuesConsumer,
SharedSessionContractImplementor session) {
return delegate.forEachDisassembledJdbcValue( value, clause, offset, valuesConsumer, session );
}
@Override
public int forEachJdbcValue(
Object value,
Clause clause,
int offset,
JdbcValuesConsumer consumer,
SharedSessionContractImplementor session) {
return delegate.forEachJdbcValue( value, clause, offset, consumer, session );
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
return delegate.forEachJdbcType( offset, action );
}
@Override
public EntityPersister getEntityPersister() {
return delegate.getEntityMappingType().getEntityPersister();
}
@Override
public String getEntityName() {
return delegate.getEntityMappingType().getEntityName();
}
@Override
public void visitQuerySpaces(Consumer<String> querySpaceConsumer) {
delegate.getEntityMappingType().visitQuerySpaces( querySpaceConsumer );
}
@Override
public AttributeMapping findDeclaredAttributeMapping(String name) {
return delegate.getEntityMappingType().findDeclaredAttributeMapping( name );
}
@Override
public Collection<AttributeMapping> getDeclaredAttributeMappings() {
return delegate.getEntityMappingType().getDeclaredAttributeMappings();
}
@Override
public void visitDeclaredAttributeMappings(Consumer<? super AttributeMapping> action) {
delegate.getEntityMappingType().visitDeclaredAttributeMappings( action );
}
@Override
public EntityIdentifierMapping getIdentifierMapping() {
return identifierMapping;
}
@Override
public EntityDiscriminatorMapping getDiscriminatorMapping() {
return delegate.getEntityMappingType().getDiscriminatorMapping();
}
@Override
public Object getDiscriminatorValue() {
return delegate.getEntityMappingType().getDiscriminatorValue();
}
@Override
public String getSubclassForDiscriminatorValue(Object value) {
return delegate.getEntityMappingType().getSubclassForDiscriminatorValue( value );
}
@Override
public EntityVersionMapping getVersionMapping() {
return delegate.getEntityMappingType().getVersionMapping();
}
@Override
public NaturalIdMapping getNaturalIdMapping() {
return delegate.getEntityMappingType().getNaturalIdMapping();
}
@Override
public EntityRowIdMapping getRowIdMapping() {
return delegate.getEntityMappingType().getRowIdMapping();
}
@Override
public void visitConstraintOrderedTables(ConstraintOrderedTableConsumer consumer) {
delegate.getEntityMappingType().visitConstraintOrderedTables( consumer );
}
@Override
public NaturalIdLoader<?> getNaturalIdLoader() {
return delegate.getEntityMappingType().getNaturalIdLoader();
}
@Override
public MultiNaturalIdLoader<?> getMultiNaturalIdLoader() {
return delegate.getEntityMappingType().getMultiNaturalIdLoader();
}
@Override
public EntityMappingType getEntityMappingType() {
return this;
}
@Override
public SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup) {
return delegate instanceof TableGroupJoinProducer
? ( (TableGroupJoinProducer) delegate ).getDefaultSqlAstJoinType( parentTableGroup )
: null;
}
@Override
public boolean isSimpleJoinPredicate(Predicate predicate) {
return delegate instanceof TableGroupJoinProducer
? ( (TableGroupJoinProducer) delegate ).isSimpleJoinPredicate( predicate )
: false;
}
}

View File

@ -0,0 +1,122 @@
/*
* 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.lang.reflect.Member;
import org.hibernate.Incubating;
import org.hibernate.metamodel.AttributeClassification;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.SimpleDomainType;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmSingularJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmJoin;
import org.hibernate.type.descriptor.java.JavaType;
/**
* @author Christian Beikov
*/
@Incubating
public class AnonymousTuplePersistentSingularAttribute<O, J> extends AnonymousTupleSqmPathSource<J> implements
SingularPersistentAttribute<O, J> {
private final SingularPersistentAttribute<O, J> delegate;
public AnonymousTuplePersistentSingularAttribute(
String localPathName,
SqmPath<J> path,
SingularPersistentAttribute<O, J> delegate) {
super( localPathName, path );
this.delegate = delegate;
}
@Override
public SqmJoin createSqmJoin(
SqmFrom lhs,
SqmJoinType joinType,
String alias,
boolean fetched,
SqmCreationState creationState) {
return new SqmSingularJoin<>(
lhs,
this,
alias,
joinType,
fetched,
creationState.getCreationContext().getNodeBuilder()
);
}
@Override
public SimpleDomainType<J> getType() {
return delegate.getType();
}
@Override
public ManagedDomainType<O> getDeclaringType() {
return delegate.getDeclaringType();
}
@Override
public boolean isId() {
return delegate.isId();
}
@Override
public boolean isVersion() {
return delegate.isVersion();
}
@Override
public boolean isOptional() {
return delegate.isOptional();
}
@Override
public JavaType<J> getAttributeJavaType() {
return delegate.getAttributeJavaType();
}
@Override
public AttributeClassification getAttributeClassification() {
return delegate.getAttributeClassification();
}
@Override
public SimpleDomainType<?> getKeyGraphType() {
return delegate.getKeyGraphType();
}
@Override
public String getName() {
return delegate.getName();
}
@Override
public PersistentAttributeType getPersistentAttributeType() {
return delegate.getPersistentAttributeType();
}
@Override
public Member getJavaMember() {
return delegate.getJavaMember();
}
@Override
public boolean isAssociation() {
return delegate.isAssociation();
}
@Override
public boolean isCollection() {
return delegate.isCollection();
}
}

View File

@ -0,0 +1,81 @@
/*
* 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 org.hibernate.Incubating;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.spi.NavigablePath;
import org.hibernate.type.descriptor.java.JavaType;
/**
* @author Christian Beikov
*/
@Incubating
public class AnonymousTupleSimpleSqmPathSource<J> implements SqmPathSource<J> {
private final String localPathName;
private final DomainType<J> domainType;
private final BindableType jpaBindableType;
public AnonymousTupleSimpleSqmPathSource(
String localPathName,
DomainType<J> domainType,
BindableType jpaBindableType) {
this.localPathName = localPathName;
this.domainType = domainType;
this.jpaBindableType = jpaBindableType;
}
@Override
public Class<J> getBindableJavaType() {
return domainType.getBindableJavaType();
}
@Override
public String getPathName() {
return localPathName;
}
@Override
public DomainType<?> getSqmPathType() {
return domainType;
}
@Override
public BindableType getBindableType() {
return jpaBindableType;
}
@Override
public JavaType<J> getExpressibleJavaType() {
return domainType.getExpressibleJavaType();
}
@Override
public SqmPathSource<?> findSubPathSource(String name) {
throw new IllegalStateException( "Basic paths cannot be dereferenced" );
}
@Override
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
final NavigablePath navigablePath;
if ( intermediatePathSource == null ) {
navigablePath = lhs.getNavigablePath().append( getPathName() );
}
else {
navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( getPathName() );
}
return new SqmBasicValuedSimplePath<>(
navigablePath,
this,
lhs,
lhs.nodeBuilder()
);
}
}

View File

@ -0,0 +1,108 @@
/*
* 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 org.hibernate.Incubating;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.PersistentAttribute;
import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.spi.NavigablePath;
import org.hibernate.type.descriptor.java.JavaType;
/**
* @author Christian Beikov
*/
@Incubating
public class AnonymousTupleSqmPathSource<J> implements SqmPathSource<J> {
private final String localPathName;
private final SqmPath<J> path;
public AnonymousTupleSqmPathSource(
String localPathName,
SqmPath<J> path) {
this.localPathName = localPathName;
this.path = path;
}
@Override
public Class<J> getBindableJavaType() {
return path.getNodeJavaType().getJavaTypeClass();
}
@Override
public String getPathName() {
return localPathName;
}
@Override
public DomainType<J> getSqmPathType() {
//noinspection unchecked
return (DomainType<J>) path.getNodeType().getSqmPathType();
}
@Override
public BindableType getBindableType() {
return path.getNodeType().getBindableType();
}
@Override
public JavaType<J> getExpressibleJavaType() {
return path.getNodeJavaType();
}
@Override
public SqmPathSource<?> findSubPathSource(String name) {
return path.getNodeType().findSubPathSource( name );
}
@Override
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
final NavigablePath navigablePath;
if ( intermediatePathSource == null ) {
navigablePath = lhs.getNavigablePath().append( getPathName() );
}
else {
navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( getPathName() );
}
final SqmPathSource<J> nodeType = path.getNodeType();
if ( nodeType instanceof BasicSqmPathSource<?> ) {
return new SqmBasicValuedSimplePath<>(
navigablePath,
this,
lhs,
lhs.nodeBuilder()
);
}
else if ( nodeType instanceof EmbeddedSqmPathSource<?> ) {
return new SqmEmbeddedValuedSimplePath<>(
navigablePath,
this,
lhs,
lhs.nodeBuilder()
);
}
else if ( nodeType instanceof EntitySqmPathSource<?> || nodeType instanceof EntityDomainType<?>
|| nodeType instanceof PersistentAttribute<?, ?> && nodeType.getSqmPathType() instanceof EntityDomainType<?> ) {
return new SqmEntityValuedSimplePath<>(
navigablePath,
this,
lhs,
lhs.nodeBuilder()
);
}
throw new UnsupportedOperationException( "Unsupported path source: " + nodeType );
}
}

View File

@ -0,0 +1,317 @@
/*
* 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.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.hibernate.Incubating;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupProducer;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.metamodel.Attribute;
/**
* The table group producer for an anonymous tuple type.
*
* Model part names are determined based on the tuple type component names.
* The kind and type of the model parts is based on the type of the underlying selection.
*
* @author Christian Beikov
*/
@Incubating
public class AnonymousTupleTableGroupProducer implements TableGroupProducer, MappingType {
private final String aliasStem;
private final JavaType<?> javaTypeDescriptor;
private final Map<String, ModelPart> modelParts;
private final Set<String> compatibleTableExpressions;
public AnonymousTupleTableGroupProducer(
AnonymousTupleType<?> tupleType,
String aliasStem,
List<SqlSelection> sqlSelections,
FromClauseAccess fromClauseAccess) {
this.aliasStem = aliasStem;
this.javaTypeDescriptor = tupleType.getExpressibleJavaType();
final Set<String> compatibleTableExpressions = new HashSet<>();
// The empty table expression is the default for derived model parts
compatibleTableExpressions.add( "" );
final int componentCount = tupleType.componentCount();
final Map<String, ModelPart> modelParts = CollectionHelper.linkedMapOfSize( componentCount );
int selectionIndex = 0;
for ( int i = 0; i < componentCount; i++ ) {
final SqmSelectableNode<?> selectableNode = tupleType.getSelectableNode( i );
final String partName = tupleType.getComponentName( i );
final SqlSelection sqlSelection = sqlSelections.get( i );
final ModelPart modelPart;
if ( selectableNode instanceof SqmPath<?> ) {
final SqmPath<?> sqmPath = (SqmPath<?>) selectableNode;
final TableGroup tableGroup = fromClauseAccess.findTableGroup( sqmPath.getNavigablePath() );
modelPart = createModelPart(
selectableNode.getExpressible(),
sqmPath.getNodeType().getSqmPathType(),
sqlSelections,
selectionIndex,
partName,
partName,
tableGroup == null ? null : getModelPart( tableGroup ),
compatibleTableExpressions
);
}
else {
modelPart = new AnonymousTupleBasicValuedModelPart(
partName,
partName,
selectableNode.getExpressible(),
sqlSelection.getExpressionType()
.getJdbcMappings()
.get( 0 )
);
}
modelParts.put( partName, modelPart );
selectionIndex += modelPart.getJdbcTypeCount();
}
this.modelParts = modelParts;
this.compatibleTableExpressions = compatibleTableExpressions;
}
private ModelPart getModelPart(TableGroup tableGroup) {
if ( tableGroup instanceof LazyTableGroup && ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup() != null ) {
return ( (LazyTableGroup) tableGroup ).getUnderlyingTableGroup().getModelPart();
}
return tableGroup.getModelPart();
}
private ModelPart createModelPart(
SqmExpressible<?> sqmExpressible,
DomainType<?> domainType,
List<SqlSelection> sqlSelections,
int selectionIndex,
String selectionExpression,
String partName,
ModelPart existingModelPart,
Set<String> compatibleTableExpressions) {
if ( domainType instanceof EntityDomainType<?> ) {
final EntityValuedModelPart existingModelPartContainer = (EntityValuedModelPart) existingModelPart;
final EntityIdentifierMapping identifierMapping = existingModelPartContainer.getEntityMappingType()
.getIdentifierMapping();
final EntityIdentifierMapping newIdentifierMapping;
if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) {
if ( identifierMapping.getPartMappingType() instanceof ManagedMappingType ) {
// todo: implement
throw new UnsupportedOperationException("Support for embedded id in anonymous tuples is not yet implemented");
}
else {
newIdentifierMapping = new AnonymousTupleBasicEntityIdentifierMapping(
selectionExpression + "_" + ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName(),
sqmExpressible,
sqlSelections.get( selectionIndex )
.getExpressionType()
.getJdbcMappings()
.get( 0 ),
(BasicEntityIdentifierMapping) identifierMapping
);
}
}
else {
// todo: implement
throw new UnsupportedOperationException("Support for id-class in anonymous tuples is not yet implemented");
}
if ( existingModelPartContainer instanceof ToOneAttributeMapping ) {
// We take "ownership" of FK columns by reporting the derived table group is compatible
compatibleTableExpressions.add( ( (ToOneAttributeMapping) existingModelPart ).getIdentifyingColumnsTableExpression() );
}
return new AnonymousTupleEntityValuedModelPart(
newIdentifierMapping,
domainType,
selectionExpression,
existingModelPartContainer
);
}
else if ( domainType instanceof ManagedDomainType<?> ) {
//noinspection unchecked
final Set<Attribute<?, ?>> attributes = (Set<Attribute<?, ?>>) ( (ManagedDomainType<?>) domainType ).getAttributes();
final Map<String, ModelPart> modelParts = CollectionHelper.linkedMapOfSize( attributes.size() );
final EmbeddableValuedModelPart modelPartContainer = (EmbeddableValuedModelPart) existingModelPart;
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 );
}
return new AnonymousTupleEmbeddableValuedModelPart( modelParts, domainType, selectionExpression, modelPartContainer );
}
else {
return new AnonymousTupleBasicValuedModelPart(
partName,
selectionExpression,
sqmExpressible,
sqlSelections.get( selectionIndex )
.getExpressionType()
.getJdbcMappings()
.get( 0 )
);
}
}
public Set<String> getCompatibleTableExpressions() {
return compatibleTableExpressions;
}
@Override
public MappingType getPartMappingType() {
return this;
}
@Override
public JavaType<?> getMappedJavaType() {
return javaTypeDescriptor;
}
@Override
public String getPartName() {
return null;
}
@Override
public NavigableRole getNavigableRole() {
return null;
}
@Override
public EntityMappingType findContainingEntityMapping() {
return null;
}
@Override
public ModelPart findSubPart(String name, EntityMappingType treatTargetType) {
return modelParts.get( name );
}
@Override
public void visitSubParts(Consumer<ModelPart> consumer, EntityMappingType treatTargetType) {
for ( ModelPart modelPart : modelParts.values() ) {
consumer.accept( modelPart );
}
}
@Override
public String getSqlAliasStem() {
return aliasStem;
}
@Override
public JavaType<?> getJavaType() {
return javaTypeDescriptor;
}
//--------------------------------
// Support for using the anonymous tuple as table reference directly somewhere is not yet implemented
//--------------------------------
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
throw new UnsupportedOperationException( "Not yet implemented" );
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState) {
throw new UnsupportedOperationException( "Not yet implemented" );
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState,
BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
throw new UnsupportedOperationException( "Not yet implemented" );
}
@Override
public void breakDownJdbcValues(
Object domainValue,
JdbcValueConsumer valueConsumer,
SharedSessionContractImplementor session) {
throw new UnsupportedOperationException( "Not yet implemented" );
}
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
throw new UnsupportedOperationException( "Not yet implemented" );
}
@Override
public int forEachDisassembledJdbcValue(
Object value,
Clause clause,
int offset,
JdbcValuesConsumer valuesConsumer,
SharedSessionContractImplementor session) {
throw new UnsupportedOperationException( "Not yet implemented" );
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
throw new UnsupportedOperationException( "Not yet implemented" );
}
}

View File

@ -0,0 +1,193 @@
/*
* 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.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.hibernate.Incubating;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.UnsupportedMappingException;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.metamodel.model.domain.TupleType;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.ObjectArrayJavaType;
/**
* @author Christian Beikov
*/
@Incubating
public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, ReturnableType<T>, SqmPathSource<T> {
private final ObjectArrayJavaType javaTypeDescriptor;
private final SqmSelectableNode<?>[] components;
private final Map<String, Integer> componentIndexMap;
public AnonymousTupleType(SqmSubQuery<T> subQuery) {
this( extractSqmExpressibles( subQuery ) );
}
public AnonymousTupleType(SqmSelectableNode<?>[] components) {
this.components = components;
this.javaTypeDescriptor = new ObjectArrayJavaType( getTypeDescriptors( components ) );
final Map<String, Integer> map = CollectionHelper.linkedMapOfSize( components.length );
for ( int i = 0; i < components.length; i++ ) {
final SqmSelectableNode<?> component = components[i];
final String alias = component.getAlias();
if ( alias == null ) {
throw new IllegalArgumentException( "Component at index " + i + " has no alias, but alias is required!" );
}
map.put( alias, i );
}
this.componentIndexMap = map;
}
private static SqmSelectableNode<?>[] extractSqmExpressibles(SqmSubQuery<?> subQuery) {
final SqmSelectClause selectClause = subQuery.getQuerySpec().getSelectClause();
if ( selectClause == null || selectClause.getSelectionItems().isEmpty() ) {
throw new IllegalArgumentException( "Sub query has no selection items!" );
}
// todo: right now, we "snapshot" the state of the sub query when creating this type, but maybe we shouldn't?
// i.e. what if the sub query changes later on? Or should we somehow mark the sub query to signal,
// that changes to the select clause are invalid after a certain point?
return selectClause.getSelectionItems().toArray( SqmSelectableNode[]::new );
}
private static JavaType<?>[] getTypeDescriptors(SqmSelectableNode<?>[] components) {
final JavaType<?>[] typeDescriptors = new JavaType<?>[components.length];
for ( int i = 0; i < components.length; i++ ) {
typeDescriptors[i] = components[i].getExpressible().getExpressibleJavaType();
}
return typeDescriptors;
}
public AnonymousTupleTableGroupProducer resolveTableGroupProducer(
String aliasStem,
List<SqlSelection> sqlSelections,
FromClauseAccess fromClauseAccess) {
return new AnonymousTupleTableGroupProducer( this, aliasStem, sqlSelections, fromClauseAccess );
}
@Override
public int componentCount() {
return components.length;
}
@Override
public String getComponentName(int index) {
return components[index].getAlias();
}
@Override
public List<String> getComponentNames() {
return new ArrayList<>( componentIndexMap.keySet() );
}
@Override
public SqmExpressible<?> get(int index) {
return components[index].getExpressible();
}
@Override
public SqmExpressible<?> get(String componentName) {
final Integer index = componentIndexMap.get( componentName );
return index == null ? null : components[index].getExpressible();
}
public SqmSelectableNode<?> getSelectableNode(int index) {
return components[index];
}
@Override
public SqmPathSource<?> findSubPathSource(String name) {
final Integer index = componentIndexMap.get( name );
if ( index == null ) {
return null;
}
final SqmSelectableNode<?> component = components[index];
if ( component instanceof SqmPath<?> ) {
final SqmPath<?> sqmPath = (SqmPath<?>) component;
if ( sqmPath.getNodeType() instanceof SingularPersistentAttribute<?, ?> ) {
//noinspection unchecked,rawtypes
return new AnonymousTuplePersistentSingularAttribute( name, sqmPath, (SingularPersistentAttribute<?, ?>) sqmPath.getNodeType() );
}
else {
return new AnonymousTupleSqmPathSource<>( name, sqmPath );
}
}
else {
return new AnonymousTupleSimpleSqmPathSource<>(
name,
(DomainType<? extends Object>) component.getExpressible(),
BindableType.SINGULAR_ATTRIBUTE
);
}
}
@Override
public JavaType<T> getExpressibleJavaType() {
//noinspection unchecked
return (JavaType<T>) javaTypeDescriptor;
}
@Override
public BindableType getBindableType() {
return BindableType.ENTITY_TYPE;
}
@Override
public PersistenceType getPersistenceType() {
return PersistenceType.ENTITY;
}
@Override
public String getPathName() {
return "tuple" + System.identityHashCode( this );
}
@Override
public DomainType<?> getSqmPathType() {
return this;
}
@Override
public SqmPath<T> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
throw new UnsupportedMappingException(
"AnonymousTupleType cannot be used to create an SqmPath - that would be an SqmFrom which are created directly"
);
}
@Override
public Class<T> getBindableJavaType() {
//noinspection unchecked
return (Class<T>) javaTypeDescriptor.getJavaType();
}
@Override
public Class<T> getJavaType() {
return getBindableJavaType();
}
@Override
public String toString() {
return "AnonymousTupleType" + Arrays.toString( components );
}
}

View File

@ -15,6 +15,8 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.from.SqmDerivedJoin;
import org.hibernate.spi.NavigablePath;
import org.hibernate.query.hql.spi.SqmCreationOptions;
import org.hibernate.query.hql.spi.SqmCreationProcessingState;
@ -373,7 +375,7 @@ public class QuerySplitter {
pathSource = mappedDescriptor;
}
else {
pathSource = sqmRoot.getReferencedPathSource();
pathSource = sqmRoot.getModel();
}
final SqmRoot<?> copy = new SqmRoot<>(
pathSource,
@ -390,6 +392,26 @@ public class QuerySplitter {
return copy;
}
@Override
public SqmDerivedRoot<?> visitRootDerived(SqmDerivedRoot<?> sqmRoot) {
SqmFrom<?, ?> sqmFrom = sqmFromCopyMap.get( sqmRoot );
if ( sqmFrom != null ) {
return (SqmDerivedRoot<?>) sqmFrom;
}
final SqmDerivedRoot<?> copy = new SqmDerivedRoot<>(
(SqmSubQuery<?>) sqmRoot.getQueryPart().accept( this ),
sqmRoot.getExplicitAlias(),
sqmRoot.isLateral()
);
getProcessingStateStack().getCurrent().getPathRegistry().register( copy );
sqmFromCopyMap.put( sqmRoot, copy );
sqmPathCopyMap.put( sqmRoot.getNavigablePath(), copy );
if ( currentFromClauseCopy != null ) {
currentFromClauseCopy.addRoot( copy );
}
return copy;
}
@Override
public SqmCrossJoin<?> visitCrossJoin(SqmCrossJoin<?> join) {
final SqmFrom<?, ?> sqmFrom = sqmFromCopyMap.get( join );
@ -464,6 +486,27 @@ public class QuerySplitter {
return copy;
}
@Override
public SqmDerivedJoin<?> visitQualifiedDerivedJoin(SqmDerivedJoin<?> join) {
SqmFrom<?, ?> sqmFrom = sqmFromCopyMap.get( join );
if ( sqmFrom != null ) {
return (SqmDerivedJoin<?>) sqmFrom;
}
final SqmRoot<?> sqmRoot = (SqmRoot<?>) sqmFromCopyMap.get( join.findRoot() );
final SqmDerivedJoin copy = new SqmDerivedJoin(
(SqmSubQuery<?>) join.getQueryPart().accept( this ),
join.getExplicitAlias(),
join.getSqmJoinType(),
join.isLateral(),
sqmRoot
);
getProcessingStateStack().getCurrent().getPathRegistry().register( copy );
sqmFromCopyMap.put( join, copy );
sqmPathCopyMap.put( join.getNavigablePath(), copy );
sqmRoot.addSqmJoin( copy );
return copy;
}
@Override
public SqmBasicValuedSimplePath<?> visitBasicValuedPath(SqmBasicValuedSimplePath<?> path) {
final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry();

View File

@ -105,6 +105,7 @@ import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
@ -147,6 +148,7 @@ import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmDerivedJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmFromClause;
@ -437,11 +439,11 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
dmlTargetIndex + 1
);
final SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
if ( root.getReferencedPathSource() instanceof SqmPolymorphicRootDescriptor<?> ) {
if ( root.getModel() instanceof SqmPolymorphicRootDescriptor<?> ) {
throw new SemanticException(
String.format(
"Target type '%s' in insert statement is not an entity",
root.getReferencedPathSource().getHibernateEntityName()
root.getModel().getHibernateEntityName()
)
);
}
@ -525,11 +527,11 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
final int dmlTargetIndex = versioned ? 2 : 1;
final HqlParser.TargetEntityContext dmlTargetContext = (HqlParser.TargetEntityContext) ctx.getChild( dmlTargetIndex );
final SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
if ( root.getReferencedPathSource() instanceof SqmPolymorphicRootDescriptor<?> ) {
if ( root.getModel() instanceof SqmPolymorphicRootDescriptor<?> ) {
throw new SemanticException(
String.format(
"Target type '%s' in update statement is not an entity",
root.getReferencedPathSource().getHibernateEntityName()
root.getModel().getHibernateEntityName()
)
);
}
@ -1540,7 +1542,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmRoot<?> visitEntityWithJoins(HqlParser.EntityWithJoinsContext parserSpace) {
final SqmRoot<?> sqmRoot = visitRootEntity( (HqlParser.RootEntityContext) parserSpace.getChild( 0 ) );
final SqmRoot<?> sqmRoot = (SqmRoot<?>) parserSpace.getChild( 0 ).accept( this );
final SqmFromClause fromClause = currentQuerySpec().getFromClause();
// Correlations are implicitly added to the from clause
if ( !( sqmRoot instanceof SqmCorrelation<?, ?> ) ) {
@ -1647,6 +1649,41 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return sqmRoot;
}
@Override
public SqmRoot<?> visitRootSubquery(HqlParser.RootSubqueryContext ctx) {
if ( getCreationOptions().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
"The JPA specification does not support subqueries in the from clause. " +
"Please disable the JPA query compliance if you want to use this feature.",
StrictJpaComplianceViolation.Type.FROM_SUBQUERY
);
}
final ParseTree firstChild = ctx.getChild( 0 );
final boolean lateral = ( (TerminalNode) firstChild ).getSymbol().getType() == HqlParser.LATERAL;
final int subqueryIndex = lateral ? 2 : 1;
final SqmSubQuery<?> subQuery = (SqmSubQuery<?>) ctx.getChild( subqueryIndex ).accept( this );
final ParseTree lastChild = ctx.getChild( ctx.getChildCount() - 1 );
final HqlParser.VariableContext identificationVariableDefContext;
if ( lastChild instanceof HqlParser.VariableContext ) {
identificationVariableDefContext = (HqlParser.VariableContext) lastChild;
}
else {
identificationVariableDefContext = null;
}
final String alias = applyJpaCompliance(
visitVariable( identificationVariableDefContext )
);
final SqmCreationProcessingState processingState = processingStateStack.getCurrent();
final SqmPathRegistry pathRegistry = processingState.getPathRegistry();
final SqmRoot<?> sqmRoot = new SqmDerivedRoot<>( subQuery, alias, lateral );
pathRegistry.register( sqmRoot );
return sqmRoot;
}
@Override
public String visitVariable(HqlParser.VariableContext ctx) {
if ( ctx == null ) {
@ -1775,10 +1812,11 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
break;
}
final HqlParser.JoinPathContext qualifiedJoinPathContext = parserJoin.joinPath();
final HqlParser.JoinTargetContext qualifiedJoinTargetContext = parserJoin.joinTarget();
final ParseTree lastChild = qualifiedJoinTargetContext.getChild( qualifiedJoinTargetContext.getChildCount() - 1 );
final HqlParser.VariableContext identificationVariableDefContext;
if ( qualifiedJoinPathContext.getChildCount() > 1 ) {
identificationVariableDefContext = (HqlParser.VariableContext) qualifiedJoinPathContext.getChild( 1 );
if ( lastChild instanceof HqlParser.VariableContext ) {
identificationVariableDefContext = (HqlParser.VariableContext) lastChild;
}
else {
identificationVariableDefContext = null;
@ -1799,13 +1837,36 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
this
)
);
try {
final SqmQualifiedJoin<X, ?> join;
if ( qualifiedJoinTargetContext instanceof HqlParser.JoinPathContext ) {
//noinspection unchecked
final SqmQualifiedJoin<X, ?> join = (SqmQualifiedJoin<X, ?>) qualifiedJoinPathContext.getChild( 0 ).accept( this );
join = (SqmQualifiedJoin<X, ?>) qualifiedJoinTargetContext.getChild( 0 ).accept( this );
}
else {
if ( fetch ) {
throw new SemanticException( "fetch not allowed for subquery join" );
}
if ( getCreationOptions().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
"The JPA specification does not support subqueries in the from clause. " +
"Please disable the JPA query compliance if you want to use this feature.",
StrictJpaComplianceViolation.Type.FROM_SUBQUERY
);
}
final TerminalNode terminalNode = (TerminalNode) qualifiedJoinTargetContext.getChild( 0 );
final boolean lateral = terminalNode.getSymbol().getType() == HqlParser.LATERAL;
final int subqueryIndex = lateral ? 2 : 1;
final DotIdentifierConsumer identifierConsumer = dotIdentifierConsumerStack.pop();
final SqmSubQuery<?> subQuery = (SqmSubQuery<?>) qualifiedJoinTargetContext.getChild( subqueryIndex ).accept( this );
dotIdentifierConsumerStack.push( identifierConsumer );
//noinspection unchecked,rawtypes
join = new SqmDerivedJoin( subQuery, alias, joinType, lateral, sqmRoot );
processingStateStack.getCurrent().getPathRegistry().register( join );
}
final HqlParser.JoinRestrictionContext qualifiedJoinRestrictionContext = parserJoin.joinRestriction();
if ( join instanceof SqmEntityJoin<?> ) {
if ( join instanceof SqmEntityJoin<?> || join instanceof SqmDerivedJoin<?> ) {
sqmRoot.addSqmJoin( join );
}
else if ( join instanceof SqmAttributeJoin<?, ?> ) {

View File

@ -320,20 +320,5 @@ public class SqmPathRegistryImpl implements SqmPathRegistry {
)
);
}
final SqmFrom<?, ?> registeredFromElement = sqmFromByAlias.get( alias );
if ( registeredFromElement != null ) {
if ( !registeredFromElement.equals( selection.getSelectableNode() ) ) {
throw new AliasCollisionException(
String.format(
Locale.ENGLISH,
"Alias [%s] used in select-clause [%s] also used in from-clause [%s]",
alias,
selection.getSelectableNode(),
registeredFromElement
)
);
}
}
}
}

View File

@ -16,6 +16,7 @@ import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
@ -61,6 +62,7 @@ import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmDerivedJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFromClause;
import org.hibernate.query.sqm.tree.from.SqmRoot;
@ -128,6 +130,8 @@ public interface SemanticQueryWalker<T> {
T visitRootPath(SqmRoot<?> sqmRoot);
T visitRootDerived(SqmDerivedRoot<?> sqmRoot);
T visitCrossJoin(SqmCrossJoin<?> joinedFromElement);
T visitPluralPartJoin(SqmPluralPartJoin<?, ?> joinedFromElement);
@ -136,6 +140,8 @@ public interface SemanticQueryWalker<T> {
T visitQualifiedAttributeJoin(SqmAttributeJoin<?, ?> joinedFromElement);
T visitQualifiedDerivedJoin(SqmDerivedJoin<?> joinedFromElement);
T visitBasicValuedPath(SqmBasicValuedSimplePath<?> path);
T visitEmbeddableValuedPath(SqmEmbeddedValuedSimplePath<?> path);

View File

@ -27,6 +27,7 @@ public class StrictJpaComplianceViolation extends SemanticException {
TUPLES( "use of tuples/row value constructors" ),
COLLATIONS( "use of collations" ),
SUBQUERY_ORDER_BY( "use of ORDER BY clause in subquery" ),
FROM_SUBQUERY( "use of subquery in FROM clause" ),
SET_OPERATIONS( "use of set operations" ),
LIMIT_OFFSET_CLAUSE( "use of LIMIT/OFFSET clause" ),
IDENTIFICATION_VARIABLE_NOT_DECLARED_IN_FROM_CLAUSE( "use of an alias not declared in the FROM clause" ),

View File

@ -731,7 +731,7 @@ public class QuerySqmImpl<R>
}
private NonSelectQueryPlan buildConcreteDeleteQueryPlan(@SuppressWarnings("rawtypes") SqmDeleteStatement sqmDelete) {
final EntityDomainType<?> entityDomainType = sqmDelete.getTarget().getReferencedPathSource();
final EntityDomainType<?> entityDomainType = sqmDelete.getTarget().getModel();
final String entityNameToDelete = entityDomainType.getHibernateEntityName();
final EntityPersister entityDescriptor = getSessionFactory().getRuntimeMetamodels()
.getMappingMetamodel()
@ -759,7 +759,7 @@ public class QuerySqmImpl<R>
//noinspection rawtypes
final SqmUpdateStatement sqmUpdate = (SqmUpdateStatement) getSqmStatement();
final String entityNameToUpdate = sqmUpdate.getTarget().getReferencedPathSource().getHibernateEntityName();
final String entityNameToUpdate = sqmUpdate.getTarget().getModel().getHibernateEntityName();
final EntityPersister entityDescriptor = getSessionFactory().getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( entityNameToUpdate );
@ -777,7 +777,7 @@ public class QuerySqmImpl<R>
//noinspection rawtypes
final SqmInsertStatement sqmInsert = (SqmInsertStatement) getSqmStatement();
final String entityNameToInsert = sqmInsert.getTarget().getReferencedPathSource().getHibernateEntityName();
final String entityNameToInsert = sqmInsert.getTarget().getModel().getHibernateEntityName();
final EntityPersister entityDescriptor = getSessionFactory().getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( entityNameToInsert );

View File

@ -20,6 +20,7 @@ import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
@ -65,6 +66,7 @@ import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmDerivedJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmFromClause;
@ -501,6 +503,18 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
return null;
}
@Override
public Object visitRootDerived(SqmDerivedRoot<?> sqmRoot) {
processStanza(
"derived",
"`" + sqmRoot.getNavigablePath() + "`",
() -> {
processJoins( sqmRoot );
}
);
return null;
}
private void processJoins(SqmFrom<?,?> sqmFrom) {
if ( !sqmFrom.hasJoins() ) {
return;
@ -586,6 +600,24 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
return null;
}
@Override
public Object visitQualifiedDerivedJoin(SqmDerivedJoin<?> joinedFromElement) {
if ( inJoinPredicate ) {
logWithIndentation( "-> [joined-path] - `%s`", joinedFromElement.getNavigablePath() );
}
else {
processStanza(
"derived",
"`" + joinedFromElement.getNavigablePath() + "`",
() -> {
processJoinPredicate( joinedFromElement );
processJoins( joinedFromElement );
}
);
}
return null;
}
@Override
public Object visitBasicValuedPath(SqmBasicValuedSimplePath path) {
logWithIndentation( "-> [basic-path] - `%s`", path.getNavigablePath() );

View File

@ -128,7 +128,7 @@ public class CteInsertHandler implements InsertHandler {
this.sessionFactory = sessionFactory;
final String entityName = this.sqmStatement.getTarget()
.getReferencedPathSource()
.getModel()
.getHibernateEntityName();
this.entityDescriptor = sessionFactory.getRuntimeMetamodels().getEntityMappingType( entityName );

View File

@ -27,7 +27,7 @@ public abstract class AbstractMutationHandler implements Handler {
this.sessionFactory = sessionFactory;
final String entityName = sqmDeleteOrUpdateStatement.getTarget()
.getReferencedPathSource()
.getModel()
.getHibernateEntityName();
this.entityDescriptor = sessionFactory.getRuntimeMetamodels().getEntityMappingType( entityName );

View File

@ -19,6 +19,7 @@ import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
@ -67,6 +68,7 @@ import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmDerivedJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFromClause;
import org.hibernate.query.sqm.tree.from.SqmRoot;
@ -235,7 +237,7 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
@Override
public Object visitFromClause(SqmFromClause fromClause) {
fromClause.visitRoots( this::visitRootPath );
fromClause.visitRoots( root -> root.accept( this ) );
return fromClause;
}
@ -246,6 +248,14 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
return sqmRoot;
}
@Override
public Object visitRootDerived(SqmDerivedRoot<?> sqmRoot) {
sqmRoot.getQueryPart().accept( this );
sqmRoot.visitReusablePaths( path -> path.accept( this ) );
sqmRoot.visitSqmJoins( sqmJoin -> sqmJoin.accept( this ) );
return sqmRoot;
}
@Override
public Object visitCrossJoin(SqmCrossJoin<?> joinedFromElement) {
joinedFromElement.visitReusablePaths( path -> path.accept( this ) );
@ -280,6 +290,17 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
return joinedFromElement;
}
@Override
public Object visitQualifiedDerivedJoin(SqmDerivedJoin<?> joinedFromElement) {
joinedFromElement.getQueryPart().accept( this );
joinedFromElement.visitReusablePaths( path -> path.accept( this ) );
joinedFromElement.visitSqmJoins( sqmJoin -> sqmJoin.accept( this ) );
if ( joinedFromElement.getJoinPredicate() != null ) {
joinedFromElement.getJoinPredicate().accept( this );
}
return joinedFromElement;
}
@Override
public Object visitBasicValuedPath(SqmBasicValuedSimplePath<?> path) {
return path;

View File

@ -91,10 +91,15 @@ import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.metamodel.model.convert.internal.OrdinalEnumValueConverter;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.metamodel.model.domain.SimpleDomainType;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.DiscriminatorSqmPath;
@ -162,6 +167,7 @@ import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRootJoin;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
@ -215,6 +221,7 @@ import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmDerivedJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmFromClause;
@ -371,7 +378,6 @@ import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.internal.StandardEntityGraphTraversalStateImpl;
import org.hibernate.type.BasicType;
import org.hibernate.type.JavaObjectType;
import org.hibernate.type.NullType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.EnumJavaType;
@ -386,6 +392,7 @@ import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
import org.jboss.logging.Logger;
import jakarta.persistence.TemporalType;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.SingularAttribute;
import jakarta.persistence.metamodel.Type;
@ -2403,7 +2410,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return;
}
else {
final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() );
final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getModel() );
final TableGroup parentTableGroup = fromClauseIndex.findTableGroupOnParents(
sqmRoot.getCorrelationParent().getNavigablePath()
);
@ -2486,11 +2493,39 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
log.tracef( "Already resolved SqmRoot [%s] to TableGroup", sqmRoot );
}
final QuerySpec currentQuerySpec = currentQuerySpec();
final TableGroup tableGroup;
if ( sqmRoot.isCorrelated() ) {
return;
}
final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() );
final TableGroup tableGroup;
if ( sqmRoot instanceof SqmDerivedRoot<?> ) {
final SqmDerivedRoot<?> derivedRoot = (SqmDerivedRoot<?>) sqmRoot;
final QueryPart queryPart = (QueryPart) derivedRoot.getQueryPart().accept( this );
final AnonymousTupleType<?> tupleType = (AnonymousTupleType<?>) sqmRoot.getNodeType();
final List<SqlSelection> sqlSelections = queryPart.getFirstQuerySpec().getSelectClause().getSqlSelections();
final AnonymousTupleTableGroupProducer tableGroupProducer = tupleType.resolveTableGroupProducer(
derivedRoot.getExplicitAlias(),
sqlSelections,
lastPoppedFromClauseIndex
);
final List<String> columnNames = determineColumnNames( tupleType );
final SqlAliasBase sqlAliasBase = getSqlAliasBaseGenerator().createSqlAliasBase(
derivedRoot.getExplicitAlias() == null ? "derived" : derivedRoot.getExplicitAlias()
);
final String identifierVariable = sqlAliasBase.generateNewAlias();
tableGroup = new QueryPartTableGroup(
derivedRoot.getNavigablePath(),
tableGroupProducer,
queryPart,
identifierVariable,
columnNames,
tableGroupProducer.getCompatibleTableExpressions(),
derivedRoot.isLateral(),
true,
creationContext.getSessionFactory()
);
}
else {
final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getModel() );
tableGroup = entityDescriptor.createRootTableGroup(
true,
sqmRoot.getNavigablePath(),
@ -2511,6 +2546,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
null,
this
);
}
log.tracef( "Resolved SqmRoot [%s] to new TableGroup [%s]", sqmRoot, tableGroup );
@ -2520,6 +2556,26 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
consumeJoins( sqmRoot, fromClauseIndex, tableGroup );
}
private List<String> determineColumnNames(AnonymousTupleType<?> tupleType) {
final int componentCount = tupleType.componentCount();
final List<String> columnNames = new ArrayList<>( componentCount );
for ( int i = 0; i < componentCount; i++ ) {
final SqmSelectableNode<?> selectableNode = tupleType.getSelectableNode( i );
final String componentName = tupleType.getComponentName( i );
if ( selectableNode instanceof SqmPath<?> ) {
addColumnNames(
columnNames,
( (SqmPath<?>) selectableNode ).getNodeType().getSqmPathType(),
componentName
);
}
else {
columnNames.add( componentName );
}
}
return columnNames;
}
private void consumeJoins(SqmRoot<?> sqmRoot, FromClauseIndex fromClauseIndex, TableGroup tableGroup) {
if ( sqmRoot.getOrderedJoins() == null ) {
consumeExplicitJoins( sqmRoot, tableGroup );
@ -2656,6 +2712,9 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else if ( sqmJoin instanceof SqmEntityJoin<?> ) {
return consumeEntityJoin( ( (SqmEntityJoin<?>) sqmJoin ), lhsTableGroup, transitive );
}
else if ( sqmJoin instanceof SqmDerivedJoin<?> ) {
return consumeDerivedJoin( ( (SqmDerivedJoin<?>) sqmJoin ), lhsTableGroup, transitive );
}
else if ( sqmJoin instanceof SqmPluralPartJoin<?, ?> ) {
return consumePluralPartJoin( ( (SqmPluralPartJoin<?, ?>) sqmJoin ), ownerTableGroup, transitive );
}
@ -2888,6 +2947,84 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return tableGroup;
}
private TableGroup consumeDerivedJoin(SqmDerivedJoin<?> sqmJoin, TableGroup parentTableGroup, boolean transitive) {
final QueryPart queryPart = (QueryPart) sqmJoin.getQueryPart().accept( this );
final AnonymousTupleType<?> tupleType = (AnonymousTupleType<?>) sqmJoin.getNodeType();
final List<SqlSelection> sqlSelections = queryPart.getFirstQuerySpec().getSelectClause().getSqlSelections();
final AnonymousTupleTableGroupProducer tableGroupProducer = tupleType.resolveTableGroupProducer(
sqmJoin.getExplicitAlias(),
sqlSelections,
lastPoppedFromClauseIndex
);
final List<String> columnNames = determineColumnNames( tupleType );
final SqlAliasBase sqlAliasBase = getSqlAliasBaseGenerator().createSqlAliasBase(
sqmJoin.getExplicitAlias() == null ? "derived" : sqmJoin.getExplicitAlias()
);
final String identifierVariable = sqlAliasBase.generateNewAlias();
final QueryPartTableGroup queryPartTableGroup = new QueryPartTableGroup(
sqmJoin.getNavigablePath(),
tableGroupProducer,
queryPart,
identifierVariable,
columnNames,
tableGroupProducer.getCompatibleTableExpressions(),
sqmJoin.isLateral(),
false,
creationContext.getSessionFactory()
);
getFromClauseIndex().register( sqmJoin, queryPartTableGroup );
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
queryPartTableGroup.getNavigablePath(),
sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(),
queryPartTableGroup,
null
);
// add any additional join restrictions
if ( sqmJoin.getJoinPredicate() != null ) {
final SqmJoin<?, ?> oldJoin = currentlyProcessingJoin;
currentlyProcessingJoin = sqmJoin;
tableGroupJoin.applyPredicate( visitNestedTopLevelPredicate( sqmJoin.getJoinPredicate() ) );
currentlyProcessingJoin = oldJoin;
}
// Note that we add the entity join after processing the predicate because implicit joins needed in there
// can be just ordered right before the entity join without changing the semantics
parentTableGroup.addTableGroupJoin( tableGroupJoin );
if ( transitive ) {
consumeExplicitJoins( sqmJoin, queryPartTableGroup );
}
return queryPartTableGroup;
}
private void addColumnNames(List<String> columnNames, DomainType<?> domainType, String componentName) {
if ( domainType instanceof EntityDomainType<?> ) {
final EntityDomainType<?> entityDomainType = (EntityDomainType<?>) domainType;
final SingularPersistentAttribute<?, ?> idAttribute = entityDomainType.findIdAttribute();
final String idPath;
if ( idAttribute == null ) {
idPath = componentName;
}
else {
idPath = componentName + "_" + idAttribute.getName();
}
addColumnNames( columnNames, entityDomainType.getIdType(), idPath );
}
else if ( domainType instanceof ManagedDomainType<?> ) {
for ( Attribute<?, ?> attribute : ( (ManagedDomainType<?>) domainType ).getAttributes() ) {
if ( !( attribute instanceof SingularPersistentAttribute<?, ?> ) ) {
throw new IllegalArgumentException( "Only embeddables without collections are supported!" );
}
final DomainType<?> attributeType = ( (SingularPersistentAttribute<?, ?>) attribute ).getType();
addColumnNames( columnNames, attributeType, componentName + "_" + attribute.getName() );
}
}
else {
columnNames.add( componentName );
}
}
private TableGroup consumePluralPartJoin(SqmPluralPartJoin<?, ?> sqmJoin, TableGroup lhsTableGroup, boolean transitive) {
final PluralTableGroup pluralTableGroup = (PluralTableGroup) lhsTableGroup;
final TableGroup tableGroup = getPluralPartTableGroup( pluralTableGroup, sqmJoin.getReferencedPathSource() );
@ -3184,6 +3321,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
throw new InterpretationException( "SqmRoot not yet resolved to TableGroup" );
}
@Override
public Object visitRootDerived(SqmDerivedRoot<?> sqmRoot) {
final TableGroup resolved = getFromClauseAccess().findTableGroup( sqmRoot.getNavigablePath() );
if ( resolved != null ) {
log.tracef( "SqmDerivedRoot [%s] resolved to existing TableGroup [%s]", sqmRoot, resolved );
return visitTableGroup( resolved, sqmRoot );
}
throw new InterpretationException( "SqmDerivedRoot not yet resolved to TableGroup" );
}
@Override
public Expression visitQualifiedAttributeJoin(SqmAttributeJoin<?, ?> sqmJoin) {
final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() );
@ -3195,6 +3343,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
throw new InterpretationException( "SqmAttributeJoin not yet resolved to TableGroup" );
}
@Override
public Expression visitQualifiedDerivedJoin(SqmDerivedJoin<?> sqmJoin) {
final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() );
if ( existing != null ) {
log.tracef( "SqmDerivedJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing );
return visitTableGroup( existing, sqmJoin );
}
throw new InterpretationException( "SqmDerivedJoin not yet resolved to TableGroup" );
}
@Override
public Expression visitCrossJoin(SqmCrossJoin<?> sqmJoin) {
final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() );
@ -3301,9 +3460,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else if ( modelPart instanceof EntityMappingType ) {
resultModelPart = ( (EntityMappingType) modelPart ).getIdentifierMapping();
interpretationModelPart = modelPart;
// todo: I think this will always be null anyways because EntityMappingType will only be the model part
// of a TableGroup if that is a root TableGroup, so check if we can just switch to null
parentGroupToUse = findTableGroup( tableGroup.getNavigablePath().getParent() );
parentGroupToUse = null;
}
else {
resultModelPart = modelPart;
@ -4112,15 +4269,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
creationContext
)
);
final String compatibleTableExpression;
final Set<String> compatibleTableExpressions;
if ( modelPart instanceof BasicValuedModelPart ) {
compatibleTableExpression = ( (BasicValuedModelPart) modelPart ).getContainingTableExpression();
compatibleTableExpressions = Collections.singleton( ( (BasicValuedModelPart) modelPart ).getContainingTableExpression() );
}
else if ( modelPart instanceof EmbeddableValuedModelPart ) {
compatibleTableExpression = ( (EmbeddableValuedModelPart) modelPart ).getContainingTableExpression();
compatibleTableExpressions = Collections.singleton( ( (EmbeddableValuedModelPart) modelPart ).getContainingTableExpression() );
}
else {
compatibleTableExpression = null;
compatibleTableExpressions = Collections.emptySet();
}
lateralTableGroup = new QueryPartTableGroup(
queryPath,
@ -4128,7 +4285,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
subQuerySpec,
identifierVariable,
columnNames,
compatibleTableExpression,
compatibleTableExpressions,
true,
false,
creationContext.getSessionFactory()
@ -4857,15 +5014,21 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
if ( paramSqmType instanceof SqmPath ) {
final SqmPath<?> sqmPath = (SqmPath<?>) paramSqmType;
final NavigablePath navigablePath = sqmPath.getNavigablePath();
final ModelPart modelPart;
if ( navigablePath.getParent() != null ) {
final TableGroup tableGroup = getFromClauseAccess().getTableGroup( navigablePath.getParent() );
return tableGroup.getModelPart().findSubPart(
modelPart = tableGroup.getModelPart().findSubPart(
navigablePath.getLocalName(),
null
);
}
return getFromClauseAccess().getTableGroup( navigablePath ).getModelPart();
else {
modelPart = getFromClauseAccess().getTableGroup( navigablePath ).getModelPart();
}
if ( modelPart instanceof PluralAttributeMapping ) {
return ( (PluralAttributeMapping) modelPart ).getElementDescriptor();
}
return modelPart;
}
if ( paramSqmType instanceof BasicValuedMapping ) {
@ -7066,7 +7229,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return type1;
}
private class GlobalCteContainer implements CteContainer {
private static class GlobalCteContainer implements CteContainer {
private final Map<String, CteStatement> cteStatements;
private boolean recursive;

View File

@ -24,6 +24,9 @@ import org.hibernate.metamodel.model.domain.MapPersistentAttribute;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.metamodel.model.domain.SetPersistentAttribute;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.sqm.tree.from.SqmDerivedJoin;
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
import org.hibernate.spi.NavigablePath;
import org.hibernate.query.SemanticException;
import org.hibernate.query.criteria.JpaEntityJoin;
@ -45,6 +48,7 @@ import org.hibernate.query.sqm.tree.from.SqmRoot;
import jakarta.persistence.criteria.Fetch;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.CollectionAttribute;
import jakarta.persistence.metamodel.ListAttribute;
import jakarta.persistence.metamodel.MapAttribute;
@ -99,7 +103,7 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
*/
protected AbstractSqmFrom(
NavigablePath navigablePath,
EntityDomainType<T> entityType,
SqmPathSource<T> entityType,
String alias,
NodeBuilder nodeBuilder) {
super( navigablePath, entityType, null, nodeBuilder );
@ -555,6 +559,47 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
return join;
}
@Override
public <X> JpaDerivedJoin<X> join(Subquery<X> subquery) {
return join( subquery, SqmJoinType.INNER, false, null );
}
@Override
public <X> JpaDerivedJoin<X> join(Subquery<X> subquery, SqmJoinType joinType) {
return join( subquery, joinType, false, null );
}
@Override
public <X> JpaDerivedJoin<X> joinLateral(Subquery<X> subquery) {
return join( subquery, SqmJoinType.INNER, true, null );
}
@Override
public <X> JpaDerivedJoin<X> joinLateral(Subquery<X> subquery, SqmJoinType joinType) {
return join( subquery, joinType, true, null );
}
@Override
public <X> JpaDerivedJoin<X> join(Subquery<X> subquery, SqmJoinType joinType, boolean lateral) {
return join( subquery, joinType, lateral, null );
}
public <X> JpaDerivedJoin<X> join(Subquery<X> subquery, SqmJoinType joinType, boolean lateral, String alias) {
validateComplianceFromSubQuery();
final JpaDerivedJoin<X> join = new SqmDerivedJoin<>( (SqmSubQuery<X>) subquery, alias, joinType, lateral, findRoot() );
//noinspection unchecked
addSqmJoin( (SqmJoin<T, ?>) join );
return join;
}
private void validateComplianceFromSubQuery() {
if ( nodeBuilder().getDomainModel().getJpaCompliance().isJpaQueryComplianceEnabled() ) {
throw new IllegalStateException(
"The JPA specification does not support subqueries in the from clause. " +
"Please disable the JPA query compliance if you want to use this feature." );
}
}
@Override
public Set<Fetch<T, ?>> getFetches() {
//noinspection unchecked

View File

@ -21,7 +21,7 @@ public class SqmCorrelatedRoot<T> extends SqmRoot<T> implements SqmPathWrapper<T
public SqmCorrelatedRoot(SqmRoot<T> correlationParent) {
super(
correlationParent.getNavigablePath(),
correlationParent.getReferencedPathSource(),
correlationParent.getModel(),
correlationParent.getExplicitAlias(),
correlationParent.nodeBuilder()
);

View File

@ -0,0 +1,133 @@
/*
* 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.sqm.tree.domain;
import org.hibernate.Incubating;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.PathException;
import org.hibernate.query.criteria.JpaDerivedRoot;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.spi.SqmCreationHelper;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
import org.hibernate.spi.NavigablePath;
/**
* @author Christian Beikov
*/
@Incubating
public class SqmDerivedRoot<T> extends SqmRoot<T> implements JpaDerivedRoot<T> {
private final SqmSubQuery<T> subQuery;
private final boolean lateral;
public SqmDerivedRoot(
SqmSubQuery<T> subQuery,
String alias,
boolean lateral) {
this(
SqmCreationHelper.buildRootNavigablePath( "<<derived>>", alias ),
subQuery,
lateral,
new AnonymousTupleType<>( subQuery ),
alias
);
}
protected SqmDerivedRoot(
NavigablePath navigablePath,
SqmSubQuery<T> subQuery,
boolean lateral,
SqmPathSource<T> pathSource,
String alias) {
super(
navigablePath,
pathSource,
alias,
true,
subQuery.nodeBuilder()
);
this.subQuery = subQuery;
this.lateral = lateral;
}
@Override
public SqmDerivedRoot<T> copy(SqmCopyContext context) {
final SqmDerivedRoot<T> existing = context.getCopy( this );
if ( existing != null ) {
return existing;
}
final SqmDerivedRoot<T> path = context.registerCopy(
this,
new SqmDerivedRoot<>(
getNavigablePath(),
getQueryPart().copy( context ),
isLateral(),
getReferencedPathSource(),
getExplicitAlias()
)
);
copyTo( path, context );
return path;
}
@Override
public SqmSubQuery<T> getQueryPart() {
return subQuery;
}
@Override
public boolean isLateral() {
return lateral;
}
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitRootDerived( this );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// JPA
@Override
public EntityDomainType<T> getModel() {
// Or should we throw an exception instead?
return null;
}
@Override
public SqmCorrelatedRoot<T> createCorrelation() {
// todo: implement
throw new NotYetImplementedFor6Exception( getClass());
// return new SqmCorrelatedRoot<>( this );
}
@Override
public <S extends T> SqmTreatedRoot<T, S> treatAs(Class<S> treatJavaType) throws PathException {
throw new UnsupportedOperationException( "Derived roots can not be treated!" );
}
@Override
public <S extends T> SqmTreatedRoot<T, S> treatAs(EntityDomainType<S> treatTarget) throws PathException {
throw new UnsupportedOperationException( "Derived roots can not be treated!" );
}
@Override
public <S extends T> SqmFrom<?, S> treatAs(Class<S> treatJavaType, String alias) {
throw new UnsupportedOperationException( "Derived roots can not be treated!" );
}
@Override
public <S extends T> SqmFrom<?, S> treatAs(EntityDomainType<S> treatTarget, String alias) {
throw new UnsupportedOperationException( "Derived roots can not be treated!" );
}
}

View File

@ -76,7 +76,7 @@ public class SqmTreatedRoot<T, S extends T> extends SqmRoot<S> implements SqmTre
@Override
public EntityDomainType<S> getReferencedPathSource() {
return getManagedType();
return getTreatTarget();
}
@Override

View File

@ -0,0 +1,183 @@
/*
* 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.sqm.tree.from;
import org.hibernate.Incubating;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.PathException;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.spi.SqmCreationHelper;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.domain.AbstractSqmJoin;
import org.hibernate.query.sqm.tree.domain.SqmCorrelatedEntityJoin;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedEntityJoin;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
import org.hibernate.spi.NavigablePath;
/**
* @author Christian Beikov
*/
@Incubating
public class SqmDerivedJoin<T> extends AbstractSqmJoin<T, T> implements JpaDerivedJoin<T> {
private final SqmSubQuery<T> subQuery;
private final boolean lateral;
private SqmPredicate joinPredicate;
public SqmDerivedJoin(
SqmSubQuery<T> subQuery,
String alias,
SqmJoinType joinType,
boolean lateral,
SqmRoot<?> sqmRoot) {
this(
SqmCreationHelper.buildRootNavigablePath( "<<derived>>", alias ),
subQuery,
lateral,
new AnonymousTupleType<>( subQuery ),
alias,
validateJoinType( joinType, lateral ),
sqmRoot
);
}
protected SqmDerivedJoin(
NavigablePath navigablePath,
SqmSubQuery<T> subQuery,
boolean lateral,
SqmPathSource<T> pathSource,
String alias,
SqmJoinType joinType,
SqmRoot<?> sqmRoot) {
super(
navigablePath,
pathSource,
sqmRoot,
alias,
joinType,
sqmRoot.nodeBuilder()
);
this.subQuery = subQuery;
this.lateral = lateral;
}
private static SqmJoinType validateJoinType(SqmJoinType joinType, boolean lateral) {
if ( lateral ) {
switch ( joinType ) {
case LEFT:
case INNER:
break;
default:
throw new IllegalArgumentException( "Lateral joins can only be left or inner. Illegal join type: " + joinType );
}
}
return joinType;
}
@Override
public SqmDerivedJoin<T> copy(SqmCopyContext context) {
final SqmDerivedJoin<T> existing = context.getCopy( this );
if ( existing != null ) {
return existing;
}
final SqmDerivedJoin<T> path = context.registerCopy(
this,
new SqmDerivedJoin<>(
getNavigablePath(),
subQuery,
lateral,
getReferencedPathSource(),
getExplicitAlias(),
getSqmJoinType(),
findRoot().copy( context )
)
);
copyTo( path, context );
return path;
}
protected void copyTo(SqmDerivedJoin<T> target, SqmCopyContext context) {
super.copyTo( target, context );
target.joinPredicate = joinPredicate == null ? null : joinPredicate.copy( context );
}
public SqmRoot<?> getRoot() {
return (SqmRoot<?>) super.getLhs();
}
@Override
public SqmRoot<?> findRoot() {
return getRoot();
}
@Override
public SqmSubQuery<T> getQueryPart() {
return subQuery;
}
@Override
public boolean isLateral() {
return lateral;
}
@Override
public SqmPath<?> getLhs() {
// A derived-join has no LHS
return null;
}
@Override
public SqmPredicate getJoinPredicate() {
return joinPredicate;
}
@Override
public void setJoinPredicate(SqmPredicate predicate) {
this.joinPredicate = predicate;
}
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitQualifiedDerivedJoin( this );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// JPA
@Override
public SqmCorrelatedEntityJoin<T> createCorrelation() {
// todo: implement
throw new NotYetImplementedFor6Exception(getClass());
// return new SqmCorrelatedEntityJoin<>( this );
}
@Override
public <S extends T> SqmTreatedEntityJoin<T,S> treatAs(Class<S> treatJavaType) throws PathException {
throw new UnsupportedOperationException( "Derived joins can not be treated!" );
}
@Override
public <S extends T> SqmTreatedEntityJoin<T,S> treatAs(EntityDomainType<S> treatTarget) throws PathException {
throw new UnsupportedOperationException( "Derived joins can not be treated!" );
}
@Override
public <S extends T> SqmFrom<?, S> treatAs(Class<S> treatJavaType, String alias) {
throw new UnsupportedOperationException( "Derived joins can not be treated!" );
}
@Override
public <S extends T> SqmFrom<?, S> treatAs(EntityDomainType<S> treatTarget, String alias) {
throw new UnsupportedOperationException( "Derived joins can not be treated!" );
}
}

View File

@ -59,11 +59,11 @@ public class SqmRoot<E> extends AbstractSqmFrom<E,E> implements JpaRoot<E> {
protected SqmRoot(
NavigablePath navigablePath,
EntityDomainType<E> entityType,
SqmPathSource<E> referencedNavigable,
String alias,
boolean allowJoins,
NodeBuilder nodeBuilder) {
super( navigablePath, entityType, alias, nodeBuilder );
super( navigablePath, referencedNavigable, alias, nodeBuilder );
this.allowJoins = allowJoins;
}
@ -144,13 +144,8 @@ public class SqmRoot<E> extends AbstractSqmFrom<E,E> implements JpaRoot<E> {
return this;
}
@Override
public EntityDomainType<E> getReferencedPathSource() {
return (EntityDomainType<E>) super.getReferencedPathSource();
}
public String getEntityName() {
return getReferencedPathSource().getHibernateEntityName();
return getModel().getHibernateEntityName();
}
@Override
@ -169,9 +164,14 @@ public class SqmRoot<E> extends AbstractSqmFrom<E,E> implements JpaRoot<E> {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// JPA
@Override
public EntityDomainType<E> getModel() {
return (EntityDomainType<E>) getReferencedPathSource();
}
@Override
public EntityDomainType<E> getManagedType() {
return getReferencedPathSource();
return getModel();
}
@Override
@ -188,11 +188,6 @@ public class SqmRoot<E> extends AbstractSqmFrom<E,E> implements JpaRoot<E> {
return !hasTreats();
}
@Override
public EntityDomainType<E> getModel() {
return getReferencedPathSource();
}
@Override
public <S extends E> SqmTreatedRoot<E, S> treatAs(Class<S> treatJavaType) throws PathException {
return treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ) );

View File

@ -20,12 +20,14 @@ import org.hibernate.query.sqm.tree.AbstractSqmNode;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.cte.SqmCteContainer;
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.EntityType;
/**
@ -139,6 +141,24 @@ public abstract class AbstractSqmSelectQuery<T>
}
@Override
public <X> SqmDerivedRoot<X> from(Subquery<X> subquery) {
return from( subquery, false );
}
@Override
public <X> SqmDerivedRoot<X> fromLateral(Subquery<X> subquery) {
return from( subquery, true );
}
@Override
public <X> SqmDerivedRoot<X> from(Subquery<X> subquery, boolean lateral) {
validateComplianceFromSubQuery();
final SqmDerivedRoot<X> root = new SqmDerivedRoot<>( (SqmSubQuery<X>) subquery, null, lateral );
addRoot( root );
return root;
}
private <X> SqmRoot<X> addRoot(SqmRoot<X> root) {
getQuerySpec().addRoot( root );
return root;
@ -149,6 +169,13 @@ public abstract class AbstractSqmSelectQuery<T>
return addRoot( new SqmRoot<>( (EntityDomainType<X>) entityType, null, true, nodeBuilder() ) );
}
private void validateComplianceFromSubQuery() {
if ( nodeBuilder().getDomainModel().getJpaCompliance().isJpaQueryComplianceEnabled() ) {
throw new IllegalStateException(
"The JPA specification does not support subqueries in the from clause. " +
"Please disable the JPA query compliance if you want to use this feature." );
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Selection

View File

@ -401,12 +401,14 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
@Override
public JpaCriteriaQuery<T> offset(JpaExpression<? extends Number> offset) {
validateComplianceFetchOffset();
getQueryPart().setOffset( offset );
return this;
}
@Override
public JpaCriteriaQuery<T> offset(Number offset) {
validateComplianceFetchOffset();
getQueryPart().setOffset( nodeBuilder().value( offset ) );
return this;
}
@ -419,24 +421,28 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
@Override
public JpaCriteriaQuery<T> fetch(JpaExpression<? extends Number> fetch) {
validateComplianceFetchOffset();
getQueryPart().setFetch( fetch );
return this;
}
@Override
public JpaCriteriaQuery<T> fetch(JpaExpression<? extends Number> fetch, FetchClauseType fetchClauseType) {
validateComplianceFetchOffset();
getQueryPart().setFetch( fetch, fetchClauseType );
return this;
}
@Override
public JpaCriteriaQuery<T> fetch(Number fetch) {
validateComplianceFetchOffset();
getQueryPart().setFetch( nodeBuilder().value( fetch ) );
return this;
}
@Override
public JpaCriteriaQuery<T> fetch(Number fetch, FetchClauseType fetchClauseType) {
validateComplianceFetchOffset();
getQueryPart().setFetch( nodeBuilder().value( fetch ), fetchClauseType );
return this;
}
@ -445,4 +451,12 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
public FetchClauseType getFetchClauseType() {
return getQueryPart().getFetchClauseType();
}
private void validateComplianceFetchOffset() {
if ( nodeBuilder().getDomainModel().getJpaCompliance().isJpaQueryComplianceEnabled() ) {
throw new IllegalStateException(
"The JPA specification does not support the fetch or offset clause. " +
"Please disable the JPA query compliance if you want to use this feature." );
}
}
}

View File

@ -14,8 +14,11 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.query.criteria.JpaExpression;
import org.hibernate.query.criteria.JpaOrder;
import org.hibernate.query.criteria.JpaSelection;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmExpressible;
@ -47,12 +50,14 @@ import org.hibernate.query.sqm.tree.predicate.SqmInPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.AbstractQuery;
import jakarta.persistence.criteria.CollectionJoin;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.ListJoin;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.PluralJoin;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
@ -165,6 +170,90 @@ public class SqmSubQuery<T> extends AbstractSqmSelectQuery<T> implements SqmSele
return this;
}
@Override
@SuppressWarnings("unchecked")
public SqmSubQuery<T> multiselect(Selection<?>... selections) {
validateComplianceMultiselect();
final Selection<? extends T> resultSelection;
Class<T> resultType = getResultType();
if ( resultType == null || resultType == Object.class ) {
switch ( selections.length ) {
case 0: {
throw new IllegalArgumentException(
"empty selections passed to criteria query typed as Object"
);
}
case 1: {
resultSelection = ( Selection<? extends T> ) selections[0];
break;
}
default: {
setResultType( (Class<T>) Object[].class );
resultSelection = ( Selection<? extends T> ) nodeBuilder().array( selections );
}
}
}
else if ( Tuple.class.isAssignableFrom( resultType ) ) {
resultSelection = ( Selection<? extends T> ) nodeBuilder().tuple( selections );
}
else if ( resultType.isArray() ) {
resultSelection = nodeBuilder().array( resultType, selections );
}
else {
resultSelection = nodeBuilder().construct( resultType, selections );
}
final SqmQuerySpec<T> querySpec = getQuerySpec();
if ( querySpec.getSelectClause() == null ) {
querySpec.setSelectClause( new SqmSelectClause( false, 1, nodeBuilder() ) );
}
querySpec.setSelection( (JpaSelection<T>) resultSelection );
return this;
}
@Override
@SuppressWarnings("unchecked")
public SqmSubQuery<T> multiselect(List<Selection<?>> selectionList) {
validateComplianceMultiselect();
final Selection<? extends T> resultSelection;
final Class<T> resultType = getResultType();
final List<? extends JpaSelection<?>> selections = (List<? extends JpaSelection<?>>) (List<?>) selectionList;
if ( resultType == null || resultType == Object.class ) {
switch ( selections.size() ) {
case 0: {
throw new IllegalArgumentException(
"empty selections passed to criteria query typed as Object"
);
}
case 1: {
resultSelection = ( Selection<? extends T> ) selections.get( 0 );
break;
}
default: {
setResultType( (Class<T>) Object[].class );
resultSelection = ( Selection<? extends T> ) nodeBuilder().array( selections );
}
}
}
else if ( Tuple.class.isAssignableFrom( resultType ) ) {
resultSelection = ( Selection<? extends T> ) nodeBuilder().tuple( selections );
}
else if ( resultType.isArray() ) {
resultSelection = nodeBuilder().array( resultType, selections );
}
else {
resultSelection = nodeBuilder().construct( resultType, selections );
}
final SqmQuerySpec<T> querySpec = getQuerySpec();
if ( querySpec.getSelectClause() == null ) {
querySpec.setSelectClause( new SqmSelectClause( false, 1, nodeBuilder() ) );
}
querySpec.setSelection( (JpaSelection<T>) resultSelection );
return this;
}
@Override
public SqmExpression<T> getSelection() {
final SqmSelectClause selectClause = getQuerySpec().getSelectClause();
@ -227,6 +316,117 @@ public class SqmSubQuery<T> extends AbstractSqmSelectQuery<T> implements SqmSele
return (SqmSubQuery<T>) super.having( predicates );
}
@Override
public JpaExpression<Number> getOffset() {
//noinspection unchecked
return (JpaExpression<Number>) getQueryPart().getOffset();
}
@Override
public JpaSubQuery<T> offset(JpaExpression<? extends Number> offset) {
validateComplianceFetchOffset();
getQueryPart().setOffset( offset );
return this;
}
@Override
public JpaSubQuery<T> offset(Number offset) {
validateComplianceFetchOffset();
getQueryPart().setOffset( nodeBuilder().value( offset ) );
return this;
}
@Override
public JpaExpression<Number> getFetch() {
//noinspection unchecked
return (JpaExpression<Number>) getQueryPart().getFetch();
}
@Override
public JpaSubQuery<T> fetch(JpaExpression<? extends Number> fetch) {
validateComplianceFetchOffset();
getQueryPart().setFetch( fetch );
return this;
}
@Override
public JpaSubQuery<T> fetch(JpaExpression<? extends Number> fetch, FetchClauseType fetchClauseType) {
validateComplianceFetchOffset();
getQueryPart().setFetch( fetch, fetchClauseType );
return this;
}
@Override
public JpaSubQuery<T> fetch(Number fetch) {
validateComplianceFetchOffset();
getQueryPart().setFetch( nodeBuilder().value( fetch ) );
return this;
}
@Override
public JpaSubQuery<T> fetch(Number fetch, FetchClauseType fetchClauseType) {
validateComplianceFetchOffset();
getQueryPart().setFetch( nodeBuilder().value( fetch ), fetchClauseType );
return this;
}
@Override
public FetchClauseType getFetchClauseType() {
return getQueryPart().getFetchClauseType();
}
@Override
public List<JpaOrder> getOrderList() {
//noinspection rawtypes,unchecked
return (List) getQueryPart().getSortSpecifications();
}
@Override
public JpaSubQuery<T> orderBy(Order... orders) {
validateComplianceOrderBy();
final SqmOrderByClause sqmOrderByClause = new SqmOrderByClause( orders.length );
for ( Order order : orders ) {
sqmOrderByClause.addSortSpecification( (SqmSortSpecification) order );
}
getQueryPart().setOrderByClause( sqmOrderByClause );
return this;
}
@Override
public JpaSubQuery<T> orderBy(List<Order> orders) {
validateComplianceOrderBy();
final SqmOrderByClause sqmOrderByClause = new SqmOrderByClause( orders.size() );
for ( Order order : orders ) {
sqmOrderByClause.addSortSpecification( (SqmSortSpecification) order );
}
getQueryPart().setOrderByClause( sqmOrderByClause );
return this;
}
private void validateComplianceMultiselect() {
if ( nodeBuilder().getDomainModel().getJpaCompliance().isJpaQueryComplianceEnabled() ) {
throw new IllegalStateException(
"The JPA specification does not support subqueries having multiple select items. " +
"Please disable the JPA query compliance if you want to use this feature." );
}
}
private void validateComplianceOrderBy() {
if ( nodeBuilder().getDomainModel().getJpaCompliance().isJpaQueryComplianceEnabled() ) {
throw new IllegalStateException(
"The JPA specification does not support subqueries having an order by clause. " +
"Please disable the JPA query compliance if you want to use this feature." );
}
}
private void validateComplianceFetchOffset() {
if ( nodeBuilder().getDomainModel().getJpaCompliance().isJpaQueryComplianceEnabled() ) {
throw new IllegalStateException(
"The JPA specification does not support subqueries having a fetch or offset clause. " +
"Please disable the JPA query compliance if you want to use this feature." );
}
}
@Override
public <Y> SqmRoot<Y> correlate(Root<Y> parentRoot) {
final SqmCorrelatedRoot<Y> correlated = ( (SqmRoot<Y>) parentRoot ).createCorrelation();

View File

@ -1618,6 +1618,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
protected final void visitWhereClause(Predicate whereClauseRestrictions) {
final Predicate additionalWherePredicate = this.additionalWherePredicate;
if ( whereClauseRestrictions != null && !whereClauseRestrictions.isEmpty() || additionalWherePredicate != null ) {
appendSql( " where " );
@ -1627,13 +1628,13 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
whereClauseRestrictions.accept( this );
if ( additionalWherePredicate != null ) {
appendSql( " and " );
this.additionalWherePredicate = null;
additionalWherePredicate.accept( this );
additionalWherePredicate = null;
}
}
else if ( additionalWherePredicate != null ) {
this.additionalWherePredicate = null;
additionalWherePredicate.accept( this );
additionalWherePredicate = null;
}
}
finally {
@ -3564,6 +3565,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
else if ( isParameter( expression ) ) {
final SqlAstNodeRenderingMode parameterRenderingMode = getParameterRenderingMode();
if ( parameterRenderingMode == SqlAstNodeRenderingMode.INLINE_PARAMETERS || parameterRenderingMode == SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS ) {
renderExpressionAsLiteral( expression, getJdbcParameterBindings() );
}
@ -4077,10 +4079,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
&& supportsDistinctFromPredicate() ) {
// Special case for limit 1 sub-queries to avoid double nested sub-query
// ... x(c) on x.c is not distinct from (... fetch first 1 rows only)
if ( queryPart.getFetchClauseType() == FetchClauseType.ROWS_ONLY
&& queryPart.getFetchClauseExpression() instanceof QueryLiteral<?>
&& Integer.valueOf( 1 )
.equals( ( (QueryLiteral<?>) queryPart.getFetchClauseExpression() ).getLiteralValue() ) ) {
if ( isFetchFirstRowOnly( queryPart ) ) {
return new ComparisonPredicate(
new SqlTuple( columnReferences, tableGroup.getModelPart() ),
ComparisonOperator.NOT_DISTINCT_FROM,
@ -4161,6 +4160,13 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
return null;
}
private boolean isFetchFirstRowOnly(QueryPart queryPart) {
return queryPart.getFetchClauseType() == FetchClauseType.ROWS_ONLY
&& queryPart.getFetchClauseExpression() instanceof QueryLiteral<?>
&& Integer.valueOf( 1 )
.equals( ( (QueryLiteral<?>) queryPart.getFetchClauseExpression() ).getLiteralValue() );
}
private QueryPart stripToSelectClause(QueryPart queryPart) {
if ( queryPart instanceof QueryGroup ) {
return stripToSelectClause( (QueryGroup) queryPart );
@ -4364,7 +4370,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
@Override
public void visitParameter(JdbcParameter jdbcParameter) {
switch ( parameterRenderingMode ) {
switch ( getParameterRenderingMode() ) {
case NO_PLAIN_PARAMETER:
renderCasted( jdbcParameter );
break;
@ -5104,7 +5110,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
else {
// TODO: We could use nested queries and use row numbers to emulate this
throw new IllegalArgumentException( "Can't emulate in predicate with tuples and limit/offset or set operations: " + predicate );
throw new IllegalArgumentException( "Can't emulate IN predicate with tuples and limit/offset or set operations: " + predicate );
}
}
@ -5402,6 +5408,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
return;
}
}
// If we get here, this is an equality-like comparison, though we support scalar row value comparison
// For this special case, we can rely on scalar sub query handling, given that the sub query fetches only one row
if ( isFetchFirstRowOnly( subquery ) ) {
renderComparison( lhsTuple, operator, subquery );
return;
}
}
emulateSubQueryRelationalRestrictionPredicate(
comparisonPredicate,

View File

@ -89,7 +89,8 @@ public abstract class DelegatingTableGroup implements TableGroup {
public TableReference getTableReference(
NavigablePath navigablePath,
String tableExpression,
boolean allowFkOptimization, boolean resolve) {
boolean allowFkOptimization,
boolean resolve) {
return getTableGroup().getTableReference( navigablePath, tableExpression, allowFkOptimization, resolve );
}

View File

@ -9,6 +9,7 @@ package org.hibernate.sql.ast.tree.from;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -23,7 +24,7 @@ import org.hibernate.sql.ast.tree.select.QueryPart;
public class QueryPartTableGroup extends AbstractTableGroup {
private final QueryPartTableReference queryPartTableReference;
private final String compatibleTableExpression;
private final Set<String> compatibleTableExpressions;
public QueryPartTableGroup(
NavigablePath navigablePath,
@ -40,7 +41,7 @@ public class QueryPartTableGroup extends AbstractTableGroup {
queryPart,
sourceAlias,
columnNames,
null,
Collections.emptySet(),
lateral,
canUseInnerJoins,
sessionFactory
@ -53,7 +54,8 @@ public class QueryPartTableGroup extends AbstractTableGroup {
QueryPart queryPart,
String sourceAlias,
List<String> columnNames,
String compatibleTableExpression, boolean lateral,
Set<String> compatibleTableExpressions,
boolean lateral,
boolean canUseInnerJoins,
SessionFactoryImplementor sessionFactory) {
super(
@ -64,7 +66,7 @@ public class QueryPartTableGroup extends AbstractTableGroup {
null,
sessionFactory
);
this.compatibleTableExpression = compatibleTableExpression;
this.compatibleTableExpressions = compatibleTableExpressions;
this.queryPartTableReference = new QueryPartTableReference(
queryPart,
sourceAlias,
@ -85,7 +87,7 @@ public class QueryPartTableGroup extends AbstractTableGroup {
String tableExpression,
boolean allowFkOptimization,
boolean resolve) {
if ( Objects.equals( tableExpression, compatibleTableExpression ) ) {
if ( compatibleTableExpressions.contains( tableExpression ) ) {
return getPrimaryTableReference();
}
for ( TableGroupJoin tableGroupJoin : getNestedTableGroupJoins() ) {

View File

@ -32,7 +32,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
@DomainModel( standardModels = StandardDomainModel.GAMBIT )
@ServiceRegistry
@SessionFactory
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsOrderByInSubquery.class)
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubqueryPaginationTest {
@BeforeEach
public void createTestData(SessionFactoryScope scope) {

View File

@ -0,0 +1,334 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.query;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
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.FailureExpected;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
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;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromEmbeddedIdTests.Contact.class)
@SessionFactory
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromEmbeddedIdTests {
@Test
@FailureExpected(reason = "Support for embedded id association selecting in from clause sub queries not yet supported")
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.where( root.get( "alternativeContact" ).isNotNull() );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by address.name.first" +
"limit 1" +
") a " +
"where c.alternativeContact is not null",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId1() );
assertEquals( 2, list.get( 0 ).get( 1, Contact.ContactId.class ).getId2() );
}
);
}
);
}
@Test
@FailureExpected(reason = "Support for embedded id association selecting in from clause sub queries not yet supported")
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" ) );
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",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
@FailureExpected(reason = "Support for embedded id association selecting in from clause sub queries not yet supported")
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" ) );
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",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@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" )
);
alternativeContact.setAlternativeContact( alternativeContact2 );
contact.setAlternativeContact( alternativeContact );
session.persist( alternativeContact2 );
session.persist( alternativeContact );
session.persist( contact );
} );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
/**
* @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;
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;
}
@ManyToOne(fetch = FetchType.LAZY)
public Contact getAlternativeContact() {
return alternativeContact;
}
public void setAlternativeContact(Contact alternativeContact) {
this.alternativeContact = alternativeContact;
}
@Embeddable
public static class ContactId {
private Integer id1;
private Integer id2;
public ContactId() {
}
public ContactId(Integer id1, Integer id2) {
this.id1 = id1;
this.id2 = id2;
}
public Integer getId1() {
return id1;
}
public void setId1(Integer id1) {
this.id1 = id1;
}
public Integer getId2() {
return id2;
}
public void setId2(Integer id2) {
this.id2 = id2;
}
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -0,0 +1,314 @@
/*
* 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.dialect.TiDBDialect;
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.FailureExpected;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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.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;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = SubQueryInFromIdClassTests.Contact.class)
@SessionFactory
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public class SubQueryInFromIdClassTests {
@Test
@FailureExpected(reason = "Support for id class association selecting in from clause sub queries not yet supported")
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.where( root.get( "alternativeContact" ).isNotNull() );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.contact.id1, a.contact.id2 from Contact c " +
"left join lateral (" +
"select alt as contact " +
"from c.alternativeContact alt " +
"order by address.name.first" +
"limit 1" +
") a " +
"where c.alternativeContact is not null",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
assertEquals( 2, list.get( 0 ).get( 2, Integer.class ) );
}
);
}
);
}
@Test
@FailureExpected(reason = "Support for id class association selecting in from clause sub queries not yet supported")
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" ) );
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",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
@FailureExpected(reason = "Support for id class association selecting in from clause sub queries not yet supported")
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" ) );
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",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Granny", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@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" )
);
alternativeContact.setAlternativeContact( alternativeContact2 );
contact.setAlternativeContact( alternativeContact );
session.persist( alternativeContact2 );
session.persist( alternativeContact );
session.persist( contact );
} );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createQuery( "delete Contact" ).executeUpdate();
} );
}
private <T> void verifySame(T criteriaResult, T hqlResult, Consumer<T> verifier) {
verifier.accept( criteriaResult );
verifier.accept( hqlResult );
}
/**
* @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;
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;
}
@ManyToOne(fetch = FetchType.LAZY)
public Contact getAlternativeContact() {
return alternativeContact;
}
public void setAlternativeContact(Contact alternativeContact) {
this.alternativeContact = alternativeContact;
}
@Embeddable
public static class Name {
private String first;
private String last;
public Name() {
}
public Name(String first, String last) {
this.first = first;
this.last = last;
}
@Column(name = "firstname")
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
@Column(name = "lastname")
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}
}

View File

@ -0,0 +1,442 @@
/*
* 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.time.LocalDate;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaDerivedJoin;
import org.hibernate.query.criteria.JpaDerivedRoot;
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.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.contacts.Address;
import org.hibernate.testing.orm.domain.contacts.Contact;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author Christian Beikov
*/
@DomainModel(standardModels = StandardDomainModel.CONTACTS)
@SessionFactory
public class SubQueryInFromTests {
@Test
public void testBasicRoot(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> subQueryRoot = subquery.from( Contact.class );
subquery.multiselect( subQueryRoot.get( "name" ).get( "first" ).alias( "firstName" ) );
subquery.where( cb.equal( subQueryRoot.get( "id" ), 1 ) );
final JpaRoot<Tuple> root = cq.from( subquery );
cq.multiselect( root.get( "firstName" ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select a.firstName " +
"from (" +
"select c.name.first as firstName " +
"from Contact c " +
"where c.id = 1" +
") a",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, String.class ) );
}
);
}
);
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testBasic(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> address = correlatedRoot.join( "addresses" );
subquery.multiselect( address.get( "line1" ).alias( "address" ) );
subquery.orderBy( cb.asc( address.get( "line1" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.INNER );
cq.multiselect( root.get( "name" ), a.get( "address" ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.address from Contact c " +
"join lateral (" +
"select address.line1 as address " +
"from c.addresses address " +
"order by address.line1 " +
"limit 1" +
") a",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Street 1", list.get( 0 ).get( 1, String.class ) );
}
);
}
);
}
@Test
public void testEmbeddedRoot(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> subQueryRoot = subquery.from( Contact.class );
final Join<Object, Object> address = subQueryRoot.join( "addresses" );
subquery.multiselect( subQueryRoot.get( "name" ).alias( "name" ), address.get( "postalCode" ).alias( "zip" ) );
subquery.where( cb.equal( subQueryRoot.get( "id" ), 1 ) );
final JpaDerivedRoot<Tuple> a = cq.from( subquery );
cq.multiselect( a.get( "name" ), a.get( "zip" ) );
cq.orderBy( cb.asc( a.get( "zip" ).get( "zipCode" ) ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select a.name, a.zip " +
"from (" +
"select c.name as name, address.postalCode as zip " +
"from Contact c join c.addresses address " +
"where c.id = 1" +
") a " +
"order by a.zip.zipCode",
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( 1234, list.get( 0 ).get( 1, Address.PostalCode.class ).getZipCode() );
assertEquals( "John", list.get( 1 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 5678, list.get( 1 ).get( 1, Address.PostalCode.class ).getZipCode() );
}
);
}
);
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
public void testEmbedded(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> address = correlatedRoot.join( "addresses" );
subquery.multiselect( address.get( "postalCode" ).alias( "zip" ) );
subquery.orderBy( cb.asc( address.get( "line1" ) ) );
subquery.fetch( 1 );
final JpaDerivedJoin<Tuple> a = root.joinLateral( subquery, SqmJoinType.INNER );
cq.multiselect( root.get( "name" ), a.get( "zip" ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select c.name, a.zip from Contact c " +
"join lateral (" +
"select address.postalCode as zip " +
"from c.addresses address " +
"order by address.line1 " +
"limit 1" +
") a",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 1234, list.get( 0 ).get( 1, Address.PostalCode.class ).getZipCode() );
}
);
}
);
}
@Test
public void testEntityRoot(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaSubQuery<Tuple> subquery = cq.subquery( Tuple.class );
final Root<Contact> subQueryRoot = subquery.from( Contact.class );
final Join<Object, Object> alternativeContact = subQueryRoot.join( "alternativeContact" );
subquery.multiselect( subQueryRoot.get( "name" ).alias( "name" ), alternativeContact.alias( "contact" ) );
subquery.where( cb.equal( subQueryRoot.get( "id" ), 1 ) );
final JpaDerivedRoot<Tuple> a = cq.from( subquery );
cq.multiselect( a.get( "name" ), a.get( "contact" ).get( "id" ) );
final QueryImplementor<Tuple> query = session.createQuery(
"select a.name, a.contact.id " +
"from (" +
"select c.name as name, alt as contact " +
"from Contact c join c.alternativeContact alt " +
"where c.id = 1" +
") a",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
}
);
}
);
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
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.where( cb.equal( root.get( "id" ), 1 ) );
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 " +
"where c.id = 1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( 2, list.get( 0 ).get( 1, Integer.class ) );
}
);
}
);
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
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.where( cb.equal( root.get( "id" ), 1 ) );
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 " +
"where c.id = 1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).get( 1, Contact.Name.class ).getFirst() );
}
);
}
);
}
@Test
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB db does not support subqueries for ON condition")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOrderByInCorrelatedSubquery.class)
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.where( cb.equal( root.get( "id" ), 1 ) );
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 " +
"where c.id = 1",
Tuple.class
);
verifySame(
session.createQuery( cq ).getResultList(),
query.getResultList(),
list -> {
assertEquals( 1, list.size() );
assertEquals( "John", list.get( 0 ).get( 0, Contact.Name.class ).getFirst() );
assertEquals( "Jane", list.get( 0 ).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" ),
Contact.Gender.MALE,
LocalDate.of( 1970, 1, 1 )
);
final Contact alternativeContact = new Contact(
2,
new Contact.Name( "Jane", "Doe" ),
Contact.Gender.FEMALE,
LocalDate.of( 1970, 1, 1 )
);
final Contact alternativeContact2 = new Contact(
3,
new Contact.Name( "Granny", "Doe" ),
Contact.Gender.FEMALE,
LocalDate.of( 1970, 1, 1 )
);
alternativeContact.setAlternativeContact( alternativeContact2 );
contact.setAlternativeContact( alternativeContact );
contact.setAddresses(
List.of(
new Address( "Street 1", 1234 ),
new Address( "Street 2", 5678 )
)
);
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();
} );
}
}

View File

@ -15,6 +15,8 @@ import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.testing.orm.junit.DialectFeatureCheck;
/**
* Container class for different implementation of the {@link DialectCheck} interface.
*
@ -281,9 +283,16 @@ abstract public class DialectChecks {
}
public static class SupportsArrayDataTypes implements DialectCheck {
@Override
public boolean isMatch(Dialect dialect) {
return dialect.supportsStandardArrays();
}
}
public static class SupportsOrderByInCorrelatedSubquery implements DialectCheck {
public boolean isMatch(Dialect dialect) {
return dialect.supportsOrderByInSubquery()
// For some reason, HANA doesn't support order by in correlated sub queries...
&& !( dialect instanceof AbstractHANADialect );
}
}
}

View File

@ -16,7 +16,15 @@ public class Address {
private Classification classification;
private String line1;
private String line2;
private PostalCode postalCode;
private PostalCode postalCode = new PostalCode();
public Address() {
}
public Address(String line1, int zip) {
this.line1 = line1;
this.postalCode.setZipCode( zip );
}
public Classification getClassification() {
return classification;

View File

@ -13,7 +13,9 @@ import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OrderColumn;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
@ -33,6 +35,7 @@ public class Contact {
private LocalDate birthDay;
private Contact alternativeContact;
private List<Address> addresses;
private List<PhoneNumber> phoneNumbers;
@ -81,6 +84,15 @@ public class Contact {
this.birthDay = birthDay;
}
@ManyToOne(fetch = FetchType.LAZY)
public Contact getAlternativeContact() {
return alternativeContact;
}
public void setAlternativeContact(Contact alternativeContact) {
this.alternativeContact = alternativeContact;
}
@ElementCollection
@CollectionTable( name = "contact_addresses" )
// NOTE : because of the @OrderColumn `addresses` is a List, while `phoneNumbers` is

View File

@ -24,6 +24,8 @@ import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.testing.DialectCheck;
/**
* Container class for different implementation of the {@link DialectFeatureCheck} interface.
*
@ -354,6 +356,14 @@ abstract public class DialectFeatureChecks {
}
}
public static class SupportsOrderByInCorrelatedSubquery implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return dialect.supportsOrderByInSubquery()
// For some reason, HANA doesn't support order by in correlated sub queries...
&& !( dialect instanceof AbstractHANADialect );
}
}
public static class SupportNoWait implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return dialect.supportsNoWait();