Skip to content

Commit a915962

Browse files
committed
HHH-3356 Support for normal and lateral subquery in from clause
1 parent a488e1a commit a915962

File tree

61 files changed

+4703
-155
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+4703
-155
lines changed

documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3043,4 +3043,23 @@ public void test_hql_read_only_entities_native_example() {
30433043
//end::hql-read-only-entities-native-example[]
30443044
});
30453045
}
3046+
3047+
@Test
3048+
public void test_hql_derived_root_example() {
3049+
3050+
doInJPA(this::entityManagerFactory, entityManager -> {
3051+
//tag::hql-derived-root-example[]
3052+
List<Tuple> calls = entityManager.createQuery(
3053+
"select d.owner, d.payed " +
3054+
"from (" +
3055+
"select p.person as owner, c.payment is not null as payed " +
3056+
"from Call c " +
3057+
"join c.phone p " +
3058+
"where p.number = :phoneNumber) d",
3059+
Tuple.class)
3060+
.setParameter("phoneNumber", "123-456-7890")
3061+
.getResultList();
3062+
//end::hql-derived-root-example[]
3063+
});
3064+
}
30463065
}

hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ IS : [iI] [sS];
207207
JOIN : [jJ] [oO] [iI] [nN];
208208
KEY : [kK] [eE] [yY];
209209
LAST : [lL] [aA] [sS] [tT];
210+
LATERAL : [lL] [aA] [tT] [eE] [rR] [aA] [lL];
210211
LEADING : [lL] [eE] [aA] [dD] [iI] [nN] [gG];
211212
LEFT : [lL] [eE] [fF] [tT];
212213
LIKE : [lL] [iI] [kK] [eE];

hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,15 @@ fromClause
169169
* The declaration of a root entity in 'from' clause, along with its joins
170170
*/
171171
entityWithJoins
172-
: rootEntity (join | crossJoin | jpaCollectionJoin)*
172+
: fromRoot (join | crossJoin | jpaCollectionJoin)*
173173
;
174174

175175
/**
176176
* A root entity declaration in the 'from' clause, with optional identification variable
177177
*/
178-
rootEntity
179-
: entityName variable?
178+
fromRoot
179+
: entityName variable? # RootEntity
180+
| LATERAL? LEFT_PAREN subquery RIGHT_PAREN variable? # RootSubquery
180181
;
181182

182183
/**
@@ -212,7 +213,7 @@ jpaCollectionJoin
212213
* A 'join', with an optional 'on' or 'with' clause
213214
*/
214215
join
215-
: joinType JOIN FETCH? joinPath joinRestriction?
216+
: joinType JOIN FETCH? joinTarget joinRestriction?
216217
;
217218

218219
/**
@@ -226,8 +227,9 @@ joinType
226227
/**
227228
* The joined path, with an optional identification variable
228229
*/
229-
joinPath
230-
: path variable?
230+
joinTarget
231+
: path variable? #JoinPath
232+
| LATERAL? LEFT_PAREN subquery RIGHT_PAREN variable? #JoinSubquery
231233
;
232234

233235
/**

hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,8 @@ public boolean supportsValuesListForInsert() {
10811081

10821082
@Override
10831083
public boolean supportsOrderByInSubquery() {
1084-
return false;
1084+
// Seems to work, though I don't know as of which version
1085+
return true;
10851086
}
10861087

10871088
@Override

hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,8 @@ public boolean supportsTupleDistinctCounts() {
577577

578578
@Override
579579
public boolean supportsOrderByInSubquery() {
580-
return false;
580+
// As of version 10.5 Derby supports OFFSET and FETCH as well as ORDER BY in subqueries
581+
return getVersion().isSameOrAfter( 10, 5 );
581582
}
582583

583584
@Override

hibernate-core/src/main/java/org/hibernate/dialect/HANASqlAstTranslator.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
*/
3232
public class HANASqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
3333

34+
private boolean inLateral;
35+
3436
public HANASqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
3537
super( sessionFactory, statement );
3638
}
@@ -64,7 +66,20 @@ public void visitQuerySpec(QuerySpec querySpec) {
6466

6567
@Override
6668
public void visitQueryPartTableReference(QueryPartTableReference tableReference) {
67-
emulateQueryPartTableReferenceColumnAliasing( tableReference );
69+
if ( tableReference.isLateral() && !inLateral ) {
70+
inLateral = true;
71+
emulateQueryPartTableReferenceColumnAliasing( tableReference );
72+
inLateral = false;
73+
}
74+
else {
75+
emulateQueryPartTableReferenceColumnAliasing( tableReference );
76+
}
77+
}
78+
79+
@Override
80+
protected SqlAstNodeRenderingMode getParameterRenderingMode() {
81+
// HANA does not support parameters in lateral sub queries for some reason, so inline all the parameters in this case
82+
return inLateral ? SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS : super.getParameterRenderingMode();
6883
}
6984

7085
@Override

hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import java.util.function.Consumer;
1111

1212
import org.hibernate.engine.spi.SessionFactoryImplementor;
13+
import org.hibernate.metamodel.mapping.JdbcMapping;
14+
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
1315
import org.hibernate.query.sqm.BinaryArithmeticOperator;
1416
import org.hibernate.query.sqm.ComparisonOperator;
1517
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
@@ -213,10 +215,15 @@ protected void renderSelectTupleComparison(
213215
}
214216

215217
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
218+
final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType();
219+
if ( lhsExpressionType == null ) {
220+
renderComparisonStandard( lhs, operator, rhs );
221+
return;
222+
}
216223
switch ( operator ) {
217224
case DISTINCT_FROM:
218225
case NOT_DISTINCT_FROM:
219-
if ( lhs.getExpressionType().getJdbcMappings().get( 0 ).getJdbcType() instanceof ArrayJdbcType ) {
226+
if ( lhsExpressionType.getJdbcMappings().get( 0 ).getJdbcType() instanceof ArrayJdbcType ) {
220227
// HSQL implements distinct from semantics for arrays
221228
lhs.accept( this );
222229
appendSql( operator == ComparisonOperator.DISTINCT_FROM ? "<>" : "=" );

hibernate-core/src/main/java/org/hibernate/dialect/MariaDBSqlAstTranslator.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,17 @@ protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
132132
return false;
133133
}
134134

135+
@Override
136+
protected boolean supportsIntersect() {
137+
return getDialect().getVersion().isSameOrAfter( 10, 3 );
138+
}
139+
140+
@Override
141+
protected boolean supportsDistinctFromPredicate() {
142+
// It supports a proprietary operator
143+
return true;
144+
}
145+
135146
private boolean supportsWindowFunctions() {
136147
return getDialect().getVersion().isSameOrAfter( 10, 2 );
137148
}

hibernate-core/src/main/java/org/hibernate/dialect/MySQLSqlAstTranslator.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,17 @@ protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
149149
return false;
150150
}
151151

152+
@Override
153+
protected boolean supportsIntersect() {
154+
return false;
155+
}
156+
157+
@Override
158+
protected boolean supportsDistinctFromPredicate() {
159+
// It supports a proprietary operator
160+
return true;
161+
}
162+
152163
@Override
153164
protected String getFromDual() {
154165
return " from dual";

hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.hibernate.engine.spi.SessionFactoryImplementor;
1212
import org.hibernate.internal.util.collections.Stack;
1313
import org.hibernate.metamodel.mapping.JdbcMapping;
14+
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
1415
import org.hibernate.query.sqm.BinaryArithmeticOperator;
1516
import org.hibernate.query.sqm.ComparisonOperator;
1617
import org.hibernate.query.sqm.FetchClauseType;
@@ -354,12 +355,12 @@ public void visitOver(Over<?> over) {
354355

355356
@Override
356357
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
357-
if ( lhs.getExpressionType() == null ) {
358+
final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType();
359+
if ( lhsExpressionType == null ) {
358360
renderComparisonEmulateDecode( lhs, operator, rhs );
359361
return;
360362
}
361-
final JdbcMapping lhsMapping = lhs.getExpressionType().getJdbcMappings().get( 0 );
362-
switch ( lhsMapping.getJdbcType().getJdbcTypeCode() ) {
363+
switch ( lhsExpressionType.getJdbcMappings().get( 0 ).getJdbcType().getJdbcTypeCode() ) {
363364
case SqlTypes.SQLXML:
364365
// In Oracle, XMLTYPE is not "comparable", so we have to use the xmldiff function for this purpose
365366
switch ( operator ) {

hibernate-core/src/main/java/org/hibernate/dialect/TiDBSqlAstTranslator.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import org.hibernate.sql.ast.tree.expression.Expression;
1616
import org.hibernate.sql.ast.tree.expression.Literal;
1717
import org.hibernate.sql.ast.tree.expression.Summarization;
18+
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
19+
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
1820
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
1921
import org.hibernate.sql.ast.tree.select.QueryGroup;
2022
import org.hibernate.sql.ast.tree.select.QueryPart;
@@ -76,6 +78,16 @@ public void visitQuerySpec(QuerySpec querySpec) {
7678
}
7779
}
7880

81+
@Override
82+
public void visitValuesTableReference(ValuesTableReference tableReference) {
83+
emulateValuesTableReferenceColumnAliasing( tableReference );
84+
}
85+
86+
@Override
87+
public void visitQueryPartTableReference(QueryPartTableReference tableReference) {
88+
emulateQueryPartTableReferenceColumnAliasing( tableReference );
89+
}
90+
7991
@Override
8092
public void visitOffsetFetchClause(QueryPart queryPart) {
8193
if ( !isRowNumberingCurrentQueryPart() ) {

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,40 @@ public Predicate generateJoinPredicate(
359359
TableReference keySideReference,
360360
SqlExpressionResolver sqlExpressionResolver,
361361
SqlAstCreationContext creationContext) {
362-
return getPredicate( targetSideReference, keySideReference, creationContext, targetSelectableMappings, keySelectableMappings );
362+
final Junction predicate = new Junction( Junction.Nature.CONJUNCTION );
363+
targetSelectableMappings.forEachSelectable(
364+
(i, selection) -> {
365+
final ComparisonPredicate comparisonPredicate = new ComparisonPredicate(
366+
sqlExpressionResolver.resolveSqlExpression(
367+
SqlExpressionResolver.createColumnReferenceKey(
368+
targetSideReference,
369+
selection.getSelectionExpression()
370+
),
371+
state ->
372+
new ColumnReference(
373+
targetSideReference,
374+
selection,
375+
creationContext.getSessionFactory()
376+
)
377+
),
378+
ComparisonOperator.EQUAL,
379+
sqlExpressionResolver.resolveSqlExpression(
380+
SqlExpressionResolver.createColumnReferenceKey(
381+
keySideReference,
382+
keySelectableMappings.getSelectable( i ).getSelectionExpression()
383+
),
384+
state ->
385+
new ColumnReference(
386+
keySideReference,
387+
keySelectableMappings.getSelectable( i ),
388+
creationContext.getSessionFactory()
389+
)
390+
)
391+
);
392+
predicate.add( comparisonPredicate );
393+
}
394+
);
395+
return predicate;
363396
}
364397

365398
@Override
@@ -440,34 +473,6 @@ else if ( keyExpression.equals( rhs.getColumnExpression() ) ) {
440473
return true;
441474
}
442475

443-
private Predicate getPredicate(
444-
TableReference lhs,
445-
TableReference rhs,
446-
SqlAstCreationContext creationContext,
447-
SelectableMappings lhsMappings,
448-
SelectableMappings rhsMappings) {
449-
final Junction predicate = new Junction( Junction.Nature.CONJUNCTION );
450-
lhsMappings.forEachSelectable(
451-
(i, selection) -> {
452-
final ComparisonPredicate comparisonPredicate = new ComparisonPredicate(
453-
new ColumnReference(
454-
lhs,
455-
selection,
456-
creationContext.getSessionFactory()
457-
),
458-
ComparisonOperator.EQUAL,
459-
new ColumnReference(
460-
rhs,
461-
rhsMappings.getSelectable( i ),
462-
creationContext.getSessionFactory()
463-
)
464-
);
465-
predicate.add( comparisonPredicate );
466-
}
467-
);
468-
return predicate;
469-
}
470-
471476
protected TableReference getTableReference(TableGroup lhs, TableGroup tableGroup, String table) {
472477
TableReference tableReference = lhs.getPrimaryTableReference().resolveTableReference( table );
473478
if ( tableReference != null ) {

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,30 @@ public Predicate generateJoinPredicate(
293293
SqlExpressionResolver sqlExpressionResolver,
294294
SqlAstCreationContext creationContext) {
295295
return new ComparisonPredicate(
296-
new ColumnReference(
297-
targetSideReference,
298-
targetSide.getModelPart(),
299-
creationContext.getSessionFactory()
296+
sqlExpressionResolver.resolveSqlExpression(
297+
SqlExpressionResolver.createColumnReferenceKey(
298+
targetSideReference,
299+
targetSide.getModelPart().getSelectionExpression()
300+
),
301+
state ->
302+
new ColumnReference(
303+
targetSideReference,
304+
targetSide.getModelPart(),
305+
creationContext.getSessionFactory()
306+
)
300307
),
301308
ComparisonOperator.EQUAL,
302-
new ColumnReference(
303-
keySideReference,
304-
keySide.getModelPart(),
305-
creationContext.getSessionFactory()
309+
sqlExpressionResolver.resolveSqlExpression(
310+
SqlExpressionResolver.createColumnReferenceKey(
311+
keySideReference,
312+
keySide.getModelPart().getSelectionExpression()
313+
),
314+
state ->
315+
new ColumnReference(
316+
keySideReference,
317+
keySide.getModelPart(),
318+
creationContext.getSessionFactory()
319+
)
306320
)
307321
);
308322
}

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,10 @@ public void setForeignKeyDescriptor(ForeignKeyDescriptor foreignKeyDescriptor) {
642642
&& declaringTableGroupProducer.containsTableReference( identifyingColumnsTableExpression );
643643
}
644644

645+
public String getIdentifyingColumnsTableExpression() {
646+
return identifyingColumnsTableExpression;
647+
}
648+
645649
public void setIdentifyingColumnsTableExpression(String tableExpression) {
646650
identifyingColumnsTableExpression = tableExpression;
647651
}
@@ -656,6 +660,10 @@ public ForeignKeyDescriptor.Nature getSideNature() {
656660
return sideNature;
657661
}
658662

663+
public boolean isReferenceToPrimaryKey() {
664+
return foreignKeyDescriptor.getSide( sideNature.inverse() ).getModelPart() instanceof EntityIdentifierMapping;
665+
}
666+
659667
public String getReferencedPropertyName() {
660668
return referencedPropertyName;
661669
}

hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/TupleType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88

99
import java.util.List;
1010

11+
import org.hibernate.Incubating;
1112
import org.hibernate.query.sqm.SqmExpressible;
13+
import org.hibernate.sql.ast.spi.FromClauseAccess;
14+
import org.hibernate.sql.ast.spi.SqlSelection;
15+
import org.hibernate.sql.ast.tree.from.TableGroupProducer;
1216

1317
/**
1418
* Describes any structural type without a direct java type representation.
@@ -23,4 +27,5 @@ public interface TupleType<J> extends SqmExpressible<J> {
2327

2428
SqmExpressible<?> get(int index);
2529
SqmExpressible<?> get(String componentName);
30+
2631
}

0 commit comments

Comments
 (0)