|
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