|
12 | 12 | import java.util.Map;
|
13 | 13 |
|
14 | 14 | import jakarta.persistence.metamodel.Bindable;
|
| 15 | +import org.checkerframework.checker.nullness.qual.Nullable; |
15 | 16 | import org.hibernate.Incubating;
|
16 | 17 | import org.hibernate.internal.util.collections.CollectionHelper;
|
17 | 18 | import org.hibernate.metamodel.UnsupportedMappingException;
|
|
27 | 28 | import org.hibernate.query.sqm.SqmExpressible;
|
28 | 29 | import org.hibernate.query.sqm.SqmPathSource;
|
29 | 30 | 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; |
33 | 33 | import org.hibernate.sql.ast.spi.FromClauseAccess;
|
34 | 34 | import org.hibernate.sql.ast.spi.SqlSelection;
|
35 | 35 | import org.hibernate.type.BasicType;
|
|
38 | 38 |
|
39 | 39 | import jakarta.persistence.metamodel.Attribute;
|
40 | 40 |
|
| 41 | +import static org.hibernate.internal.util.collections.CollectionHelper.linkedMapOfSize; |
| 42 | + |
41 | 43 |
|
42 | 44 | /**
|
43 | 45 | * @author Christian Beikov
|
44 | 46 | */
|
45 | 47 | @Incubating
|
46 | 48 | public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, ReturnableType<T>, SqmPathSource<T> {
|
47 | 49 |
|
48 |
| - private final ObjectArrayJavaType javaTypeDescriptor; |
| 50 | + private final JavaType<T> javaTypeDescriptor; |
| 51 | + private final @Nullable NavigablePath[] componentSourcePaths; |
49 | 52 | private final SqmSelectableNode<?>[] components;
|
| 53 | + private final String[] componentNames; |
50 | 54 | private final Map<String, Integer> componentIndexMap;
|
51 | 55 |
|
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? |
55 | 67 |
|
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 ); |
63 | 97 | if ( alias == null ) {
|
64 | 98 | throw new SemanticException( "Select item at position " + (i+1) + " in select list has no alias"
|
65 | 99 | + " (aliases are required in CTEs and in subqueries occurring in from clause)" );
|
66 | 100 | }
|
67 |
| - map.put( alias, i ); |
| 101 | + componentIndexMap.put( alias, i ); |
| 102 | + componentNames[i] = alias; |
68 | 103 | }
|
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 ); |
81 | 104 | }
|
82 | 105 |
|
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(); |
87 | 110 | }
|
88 | 111 | return typeDescriptors;
|
89 | 112 | }
|
@@ -143,7 +166,7 @@ public int componentCount() {
|
143 | 166 |
|
144 | 167 | @Override
|
145 | 168 | public String getComponentName(int index) {
|
146 |
| - return components[index].getAlias(); |
| 169 | + return componentNames[index]; |
147 | 170 | }
|
148 | 171 |
|
149 | 172 | @Override
|
|
0 commit comments