Skip to content

Commit f82bbb3

Browse files
committed
HHH-19396 Parameter deduplicateSelectionItems must be set only for temporary tables and subqueries
Selection items representing same column in (sub)query will be represented with single instance, this will hide real alias
1 parent 9430e1f commit f82bbb3

File tree

3 files changed

+61
-42
lines changed

3 files changed

+61
-42
lines changed

hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleType.java

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.Map;
1313

1414
import jakarta.persistence.metamodel.Bindable;
15+
import org.checkerframework.checker.nullness.qual.Nullable;
1516
import org.hibernate.Incubating;
1617
import org.hibernate.internal.util.collections.CollectionHelper;
1718
import org.hibernate.metamodel.UnsupportedMappingException;
@@ -27,9 +28,8 @@
2728
import org.hibernate.query.sqm.SqmExpressible;
2829
import org.hibernate.query.sqm.SqmPathSource;
2930
import org.hibernate.query.sqm.tree.domain.SqmPath;
30-
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
31-
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
32-
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
31+
import org.hibernate.query.sqm.tree.select.*;
32+
import org.hibernate.spi.NavigablePath;
3333
import org.hibernate.sql.ast.spi.FromClauseAccess;
3434
import org.hibernate.sql.ast.spi.SqlSelection;
3535
import org.hibernate.type.BasicType;
@@ -38,52 +38,75 @@
3838

3939
import jakarta.persistence.metamodel.Attribute;
4040

41+
import static org.hibernate.internal.util.collections.CollectionHelper.linkedMapOfSize;
42+
4143

4244
/**
4345
* @author Christian Beikov
4446
*/
4547
@Incubating
4648
public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, ReturnableType<T>, SqmPathSource<T> {
4749

48-
private final ObjectArrayJavaType javaTypeDescriptor;
50+
private final JavaType<T> javaTypeDescriptor;
51+
private final @Nullable NavigablePath[] componentSourcePaths;
4952
private final SqmSelectableNode<?>[] components;
53+
private final String[] componentNames;
5054
private final Map<String, Integer> componentIndexMap;
5155

52-
public AnonymousTupleType(SqmSubQuery<T> subQuery) {
53-
this( extractSqmExpressibles( subQuery ) );
54-
}
56+
public AnonymousTupleType(SqmSelectQuery<T> selectQuery) {
57+
final SqmSelectClause selectClause = selectQuery.getQueryPart()
58+
.getFirstQuerySpec()
59+
.getSelectClause();
60+
61+
if ( selectClause == null || selectClause.getSelections().isEmpty() ) {
62+
throw new IllegalArgumentException( "selectQuery has no selection items" );
63+
}
64+
// todo: right now, we "snapshot" the state of the selectQuery when creating this type, but maybe we shouldn't?
65+
// i.e. what if the selectQuery changes later on? Or should we somehow mark the selectQuery to signal,
66+
// that changes to the select clause are invalid after a certain point?
5567

56-
public AnonymousTupleType(SqmSelectableNode<?>[] components) {
57-
this.components = components;
58-
this.javaTypeDescriptor = new ObjectArrayJavaType( getTypeDescriptors( components ) );
59-
final Map<String, Integer> map = CollectionHelper.linkedMapOfSize( components.length );
60-
for ( int i = 0; i < components.length; i++ ) {
61-
final SqmSelectableNode<?> component = components[i];
62-
final String alias = component.getAlias();
68+
final List<SqmSelection<?>> selections = selectClause.getSelections();
69+
final List<SqmSelectableNode<?>> selectableNodes = new ArrayList<>();
70+
final List<String> aliases = new ArrayList<>();
71+
for ( SqmSelection<?> selection : selections ) {
72+
final boolean compound = selection.getSelectableNode().isCompoundSelection();
73+
selection.getSelectableNode().visitSubSelectableNodes( node -> {
74+
selectableNodes.add( node );
75+
if ( compound ) {
76+
aliases.add( node.getAlias() );
77+
}
78+
} );
79+
if ( !compound ) {
80+
// for compound selections we use the sub-selectable nodes aliases
81+
aliases.add( selection.getAlias() );
82+
}
83+
}
84+
85+
components = new SqmSelectableNode<?>[selectableNodes.size()];
86+
componentSourcePaths = new NavigablePath[selectableNodes.size()];
87+
componentNames = new String[selectableNodes.size()];
88+
//noinspection unchecked
89+
javaTypeDescriptor = (JavaType<T>) new ObjectArrayJavaType( getTypeDescriptors( selectableNodes ) );
90+
componentIndexMap = linkedMapOfSize( selectableNodes.size() );
91+
for ( int i = 0; i < selectableNodes.size(); i++ ) {
92+
components[i] = selectableNodes.get(i);
93+
if ( components[i] instanceof SqmPath<?> ) {
94+
componentSourcePaths[i] = ((SqmPath<?>) components[i]).getNavigablePath();
95+
}
96+
String alias = aliases.get( i );
6397
if ( alias == null ) {
6498
throw new SemanticException( "Select item at position " + (i+1) + " in select list has no alias"
6599
+ " (aliases are required in CTEs and in subqueries occurring in from clause)" );
66100
}
67-
map.put( alias, i );
101+
componentIndexMap.put( alias, i );
102+
componentNames[i] = alias;
68103
}
69-
this.componentIndexMap = map;
70-
}
71-
72-
private static SqmSelectableNode<?>[] extractSqmExpressibles(SqmSubQuery<?> subQuery) {
73-
final SqmSelectClause selectClause = subQuery.getQuerySpec().getSelectClause();
74-
if ( selectClause == null || selectClause.getSelectionItems().isEmpty() ) {
75-
throw new IllegalArgumentException( "subquery has no selection items" );
76-
}
77-
// todo: right now, we "snapshot" the state of the subquery when creating this type, but maybe we shouldn't?
78-
// i.e. what if the subquery changes later on? Or should we somehow mark the subquery to signal,
79-
// that changes to the select clause are invalid after a certain point?
80-
return selectClause.getSelectionItems().toArray( SqmSelectableNode[]::new );
81104
}
82105

83-
private static JavaType<?>[] getTypeDescriptors(SqmSelectableNode<?>[] components) {
84-
final JavaType<?>[] typeDescriptors = new JavaType<?>[components.length];
85-
for ( int i = 0; i < components.length; i++ ) {
86-
typeDescriptors[i] = components[i].getExpressible().getExpressibleJavaType();
106+
private static JavaType<?>[] getTypeDescriptors(List<SqmSelectableNode<?>> components) {
107+
final JavaType<?>[] typeDescriptors = new JavaType<?>[components.size()];
108+
for ( int i = 0; i < components.size(); i++ ) {
109+
typeDescriptors[i] = components.get( i ).getExpressible().getExpressibleJavaType();
87110
}
88111
return typeDescriptors;
89112
}
@@ -143,7 +166,7 @@ public int componentCount() {
143166

144167
@Override
145168
public String getComponentName(int index) {
146-
return components[index].getAlias();
169+
return componentNames[index];
147170
}
148171

149172
@Override

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,11 +1926,14 @@ public CteContainer visitCteContainer(SqmCteContainer consumer) {
19261926
final Collection<SqmCteStatement<?>> sqmCteStatements = consumer.getCteStatements();
19271927
cteContainer = new CteContainerImpl( cteContainer );
19281928
if ( !sqmCteStatements.isEmpty() ) {
1929+
final boolean originalDeduplicateSelectionItems = deduplicateSelectionItems;
1930+
deduplicateSelectionItems = false;
19291931
currentClauseStack.push( Clause.WITH );
19301932
for ( SqmCteStatement<?> sqmCteStatement : sqmCteStatements ) {
19311933
visitCteStatement( sqmCteStatement );
19321934
}
19331935
currentClauseStack.pop();
1936+
deduplicateSelectionItems = originalDeduplicateSelectionItems;
19341937
// Avoid leaking the processing state from CTEs to upper levels
19351938
lastPoppedFromClauseIndex = null;
19361939
lastPoppedProcessingState = null;

hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteTable.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import org.hibernate.query.sqm.SqmExpressible;
1919
import org.hibernate.query.sqm.SqmPathSource;
2020
import org.hibernate.query.sqm.tree.select.SqmSelectQuery;
21-
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
22-
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
2321
import org.hibernate.sql.ast.spi.FromClauseAccess;
2422
import org.hibernate.sql.ast.spi.SqlSelection;
2523
import org.hibernate.type.BasicType;
@@ -36,8 +34,8 @@ public class SqmCteTable<T> extends AnonymousTupleType<T> implements JpaCteCrite
3634
private SqmCteTable(
3735
String name,
3836
SqmCteStatement<T> cteStatement,
39-
SqmSelectableNode<?>[] sqmSelectableNodes) {
40-
super( sqmSelectableNodes );
37+
SqmSelectQuery<T> selectStatement) {
38+
super(selectStatement);
4139
this.name = name;
4240
this.cteStatement = cteStatement;
4341
final List<SqmCteTableColumn> columns = new ArrayList<>( componentCount() );
@@ -57,12 +55,7 @@ public static <X> SqmCteTable<X> createStatementTable(
5755
String name,
5856
SqmCteStatement<X> cteStatement,
5957
SqmSelectQuery<X> selectStatement) {
60-
final SqmSelectableNode<?>[] sqmSelectableNodes = selectStatement.getQueryPart()
61-
.getFirstQuerySpec()
62-
.getSelectClause()
63-
.getSelectionItems()
64-
.toArray( SqmSelectableNode[]::new );
65-
return new SqmCteTable<>( name, cteStatement, sqmSelectableNodes );
58+
return new SqmCteTable<>( name, cteStatement, selectStatement );
6659
}
6760

6861
@Override

0 commit comments

Comments
 (0)