Skip to content

Commit 03e589e

Browse files
jrenaatbeikov
authored andcommitted
HHH-17967
- Add test for issue (already fixed on main) - Backport the minimal necessary bits of HHH-16931 and #7883 to fix the NPE Signed-off-by: Jan Schatteman <[email protected]>
1 parent c843573 commit 03e589e

File tree

7 files changed

+136
-71
lines changed

7 files changed

+136
-71
lines changed

Diff for: hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ protected static <T> RowTransformer<T> determineRowTransformer(
218218
if ( queryOptions.getTupleTransformer() != null ) {
219219
return makeRowTransformerTupleTransformerAdapter( sqm, queryOptions );
220220
}
221-
else if ( resultType == null ) {
221+
else if ( resultType == null || resultType == Object.class ) {
222222
return RowTransformerStandardImpl.instance();
223223
}
224224
else {

Diff for: hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public abstract class AbstractSqmAttributeJoin<O,T>
3333
extends AbstractSqmQualifiedJoin<O,T>
3434
implements SqmAttributeJoin<O,T> {
3535

36-
private final boolean fetched;
36+
private boolean fetched;
3737

3838
public AbstractSqmAttributeJoin(
3939
SqmFrom<?,O> lhs,
@@ -88,6 +88,10 @@ public boolean isFetched() {
8888
return fetched;
8989
}
9090

91+
public void clearFetched() {
92+
fetched = false;
93+
}
94+
9195
@Override
9296
public <X> X accept(SemanticQueryWalker<X> walker) {
9397
return walker.visitQualifiedAttributeJoin( this );

Diff for: hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java

+24
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.function.Consumer;
1616
import java.util.stream.Collectors;
1717

18+
import org.hibernate.Internal;
1819
import org.hibernate.metamodel.mapping.ModelPartContainer;
1920
import org.hibernate.metamodel.model.domain.BagPersistentAttribute;
2021
import org.hibernate.metamodel.model.domain.EntityDomainType;
@@ -254,6 +255,29 @@ public void addSqmJoin(SqmJoin<T, ?> join) {
254255
findRoot().addOrderedJoin( join );
255256
}
256257

258+
@Internal
259+
public void removeLeftFetchJoins() {
260+
if ( joins != null ) {
261+
for ( SqmJoin<T, ?> join : new ArrayList<>(joins) ) {
262+
if ( join instanceof AbstractSqmAttributeJoin ) {
263+
final AbstractSqmAttributeJoin<T, ?> attributeJoin = (AbstractSqmAttributeJoin<T, ?>) join;
264+
if ( attributeJoin.isFetched() ) {
265+
if ( join.getSqmJoinType() == SqmJoinType.LEFT ) {
266+
joins.remove( join );
267+
final List<SqmJoin<?, ?>> orderedJoins = findRoot().getOrderedJoins();
268+
if (orderedJoins != null) {
269+
orderedJoins.remove( join );
270+
}
271+
}
272+
else {
273+
attributeJoin.clearFetched();
274+
}
275+
}
276+
}
277+
}
278+
}
279+
}
280+
257281
@Override
258282
public void visitSqmJoins(Consumer<SqmJoin<T, ?>> consumer) {
259283
if ( joins != null ) {

Diff for: hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public abstract class AbstractSqmSelectQuery<T>
4545
implements SqmSelectQuery<T> {
4646
private final Map<String, SqmCteStatement<?>> cteStatements;
4747
private SqmQueryPart<T> sqmQueryPart;
48-
private Class<T> resultType;
48+
private final Class<T> resultType;
4949

5050
public AbstractSqmSelectQuery(Class<T> resultType, NodeBuilder builder) {
5151
this( new SqmQuerySpec<>( builder ), resultType, builder );
@@ -202,8 +202,11 @@ public Class<T> getResultType() {
202202
return resultType;
203203
}
204204

205+
/**
206+
* Don't use this method. It has no effect.
207+
*/
205208
protected void setResultType(Class<T> resultType) {
206-
this.resultType = resultType;
209+
// No-op
207210
}
208211

209212
@Override
@@ -410,7 +413,6 @@ protected Selection<? extends T> getResultSelection(Selection<?>[] selections) {
410413
break;
411414
}
412415
default: {
413-
setResultType( (Class<T>) Object[].class );
414416
resultSelection = ( Selection<? extends T> ) nodeBuilder().array( selections );
415417
}
416418
}

Diff for: hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java

+43-45
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737
import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter;
3838
import org.hibernate.query.sqm.tree.expression.SqmParameter;
3939
import org.hibernate.query.sqm.tree.from.SqmFromClause;
40+
import org.hibernate.query.sqm.tree.from.SqmRoot;
4041

42+
import static org.hibernate.query.sqm.tree.SqmCopyContext.noParamCopyContext;
4143
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
4244

4345
/**
@@ -119,6 +121,10 @@ public SqmSelectStatement<T> copy(SqmCopyContext context) {
119121
if ( existing != null ) {
120122
return existing;
121123
}
124+
return createCopy( context, getResultType() );
125+
}
126+
127+
private <X> SqmSelectStatement<X> createCopy(SqmCopyContext context, Class<X> resultType) {
122128
final Set<SqmParameter<?>> parameters;
123129
if ( this.parameters == null ) {
124130
parameters = null;
@@ -129,17 +135,19 @@ public SqmSelectStatement<T> copy(SqmCopyContext context) {
129135
parameters.add( parameter.copy( context ) );
130136
}
131137
}
132-
final SqmSelectStatement<T> statement = context.registerCopy(
138+
//noinspection unchecked
139+
final SqmSelectStatement<X> statement = (SqmSelectStatement<X>) context.registerCopy(
133140
this,
134141
new SqmSelectStatement<>(
135142
nodeBuilder(),
136143
copyCteStatements( context ),
137-
getResultType(),
144+
resultType,
138145
getQuerySource(),
139146
parameters
140147
)
141148
);
142-
statement.setQueryPart( getQueryPart().copy( context ) );
149+
//noinspection unchecked
150+
statement.setQueryPart( (SqmQueryPart<X>) getQueryPart().copy( context ) );
143151
return statement;
144152
}
145153

@@ -266,9 +274,6 @@ public SqmSelectStatement<T> select(Selection<? extends T> selection) {
266274
checkSelectionIsJpaCompliant( selection );
267275
}
268276
getQuerySpec().setSelection( (JpaSelection<T>) selection );
269-
if ( getResultType() == Object.class ) {
270-
setResultType( (Class<T>) selection.getJavaType() );
271-
}
272277
return this;
273278
}
274279

@@ -309,7 +314,6 @@ public SqmSelectStatement<T> multiselect(List<Selection<?>> selectionList) {
309314
break;
310315
}
311316
default: {
312-
setResultType( (Class<T>) Object[].class );
313317
resultSelection = ( Selection<? extends T> ) nodeBuilder().array( selections );
314318
}
315319
}
@@ -460,49 +464,43 @@ private void validateComplianceFetchOffset() {
460464
}
461465

462466
@Override
463-
public JpaCriteriaQuery<Long> createCountQuery() {
464-
final SqmCopyContext context = new NoParamSqmCopyContext() {
465-
@Override
466-
public boolean copyFetchedFlag() {
467-
return false;
467+
public SqmSelectStatement<Long> createCountQuery() {
468+
final SqmSelectStatement<?> copy = createCopy( noParamCopyContext(), Object.class );
469+
final SqmQueryPart<?> queryPart = copy.getQueryPart();
470+
final SqmQuerySpec<?> querySpec;
471+
//TODO: detect queries with no 'group by', but aggregate functions
472+
// in 'select' list (we don't even need to hit the database to
473+
// know they return exactly one row)
474+
if ( queryPart.isSimpleQueryPart()
475+
&& !( querySpec = (SqmQuerySpec<?>) queryPart ).isDistinct()
476+
&& querySpec.getGroupingExpressions().isEmpty() ) {
477+
for ( SqmRoot<?> root : querySpec.getRootList() ) {
478+
root.removeLeftFetchJoins();
468479
}
469-
};
470-
final NodeBuilder nodeBuilder = nodeBuilder();
471-
final Set<SqmParameter<?>> parameters;
472-
if ( this.parameters == null ) {
473-
parameters = null;
480+
querySpec.getSelectClause().setSelection( nodeBuilder().count( new SqmStar( nodeBuilder() )) );
481+
if ( querySpec.getFetch() == null && querySpec.getOffset() == null ) {
482+
querySpec.setOrderByClause( null );
483+
}
484+
485+
return (SqmSelectStatement<Long>) copy;
474486
}
475487
else {
476-
parameters = new LinkedHashSet<>( this.parameters.size() );
477-
for ( SqmParameter<?> parameter : this.parameters ) {
478-
parameters.add( parameter.copy( context ) );
488+
final JpaSelection<?> selection = queryPart.getFirstQuerySpec().getSelection();
489+
if ( selection.isCompoundSelection() ) {
490+
char c = 'a';
491+
for ( JpaSelection<?> item : selection.getSelectionItems() ) {
492+
item.alias( Character.toString( ++c ) + '_' );
493+
}
479494
}
495+
else {
496+
selection.alias( "a_" );
497+
}
498+
final SqmSubQuery<?> subquery = new SqmSubQuery<>( copy, queryPart, null, nodeBuilder() );
499+
final SqmSelectStatement<Long> query = nodeBuilder().createQuery( Long.class );
500+
query.from( subquery );
501+
query.select( nodeBuilder().count( new SqmStar(nodeBuilder())) );
502+
return query;
480503
}
481-
final SqmSelectStatement<Long> selectStatement = new SqmSelectStatement<>(
482-
nodeBuilder,
483-
copyCteStatements( context ),
484-
Long.class,
485-
SqmQuerySource.CRITERIA,
486-
parameters
487-
);
488-
final SqmQuerySpec<Long> querySpec = new SqmQuerySpec<>( nodeBuilder );
489-
490-
final SqmSubQuery<Tuple> subquery = new SqmSubQuery<>( selectStatement, Tuple.class, nodeBuilder );
491-
final SqmQueryPart<T> queryPart = getQueryPart().copy( context );
492-
resetSelections( queryPart );
493-
// Reset the
494-
if ( queryPart.getFetch() == null && queryPart.getOffset() == null ) {
495-
queryPart.setOrderByClause( null );
496-
}
497-
//noinspection unchecked
498-
subquery.setQueryPart( (SqmQueryPart<Tuple>) queryPart );
499-
500-
querySpec.setFromClause( new SqmFromClause( 1 ) );
501-
querySpec.setSelectClause( new SqmSelectClause( false, 1, nodeBuilder ) );
502-
selectStatement.setQueryPart( querySpec );
503-
selectStatement.select( nodeBuilder.count( new SqmStar( nodeBuilder ) ) );
504-
selectStatement.from( subquery );
505-
return selectStatement;
506504
}
507505

508506
private void resetSelections(SqmQueryPart<?> queryPart) {

Diff for: hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java

-4
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,6 @@ public SqmSubQuery<T> multiselect(List<Selection<?>> selectionList) {
258258
break;
259259
}
260260
default: {
261-
setResultType( (Class<T>) Object[].class );
262261
resultSelection = ( Selection<? extends T> ) nodeBuilder().array( selections );
263262
}
264263
}
@@ -609,9 +608,6 @@ public SqmExpressible<T> getNodeType() {
609608
public void applyInferableType(SqmExpressible<?> type) {
610609
//noinspection unchecked
611610
expressibleType = (SqmExpressible<T>) type;
612-
if ( expressibleType != null && expressibleType.getExpressibleJavaType() != null ) {
613-
setResultType( expressibleType.getExpressibleJavaType().getJavaTypeClass() );
614-
}
615611
}
616612

617613
private void applyInferableType(Class<T> type) {

0 commit comments

Comments
 (0)