HHH-3356 Support for normal and lateral subquery in from clause
This commit is contained in:
parent
4947af946a
commit
341267b133
|
@ -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)
|
||||
|
||||
|
|
|
@ -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?
|
|
@ -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[]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ? "<>" : "=" );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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() ) {
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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> {
|
||||
|
||||
}
|
|
@ -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> {
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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" );
|
||||
}
|
||||
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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<?, ?> ) {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" ),
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
|
|
|
@ -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!" );
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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!" );
|
||||
}
|
||||
|
||||
}
|
|
@ -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 ) );
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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." );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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() ) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue