HHH-17967
- Add test for issue (already fixed on main) - Backport the minimal necessary bits of HHH-16931 and https://github.com/hibernate/hibernate-orm/pull/7883 to fix the NPE Signed-off-by: Jan Schatteman <jschatte@redhat.com>
This commit is contained in:
parent
c843573007
commit
03e589ef0d
|
@ -218,7 +218,7 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
|
|||
if ( queryOptions.getTupleTransformer() != null ) {
|
||||
return makeRowTransformerTupleTransformerAdapter( sqm, queryOptions );
|
||||
}
|
||||
else if ( resultType == null ) {
|
||||
else if ( resultType == null || resultType == Object.class ) {
|
||||
return RowTransformerStandardImpl.instance();
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -33,7 +33,7 @@ public abstract class AbstractSqmAttributeJoin<O,T>
|
|||
extends AbstractSqmQualifiedJoin<O,T>
|
||||
implements SqmAttributeJoin<O,T> {
|
||||
|
||||
private final boolean fetched;
|
||||
private boolean fetched;
|
||||
|
||||
public AbstractSqmAttributeJoin(
|
||||
SqmFrom<?,O> lhs,
|
||||
|
@ -88,6 +88,10 @@ public abstract class AbstractSqmAttributeJoin<O,T>
|
|||
return fetched;
|
||||
}
|
||||
|
||||
public void clearFetched() {
|
||||
fetched = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X accept(SemanticQueryWalker<X> walker) {
|
||||
return walker.visitQualifiedAttributeJoin( this );
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.util.Set;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.metamodel.model.domain.BagPersistentAttribute;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
|
@ -254,6 +255,29 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
|
|||
findRoot().addOrderedJoin( join );
|
||||
}
|
||||
|
||||
@Internal
|
||||
public void removeLeftFetchJoins() {
|
||||
if ( joins != null ) {
|
||||
for ( SqmJoin<T, ?> join : new ArrayList<>(joins) ) {
|
||||
if ( join instanceof AbstractSqmAttributeJoin ) {
|
||||
final AbstractSqmAttributeJoin<T, ?> attributeJoin = (AbstractSqmAttributeJoin<T, ?>) join;
|
||||
if ( attributeJoin.isFetched() ) {
|
||||
if ( join.getSqmJoinType() == SqmJoinType.LEFT ) {
|
||||
joins.remove( join );
|
||||
final List<SqmJoin<?, ?>> orderedJoins = findRoot().getOrderedJoins();
|
||||
if (orderedJoins != null) {
|
||||
orderedJoins.remove( join );
|
||||
}
|
||||
}
|
||||
else {
|
||||
attributeJoin.clearFetched();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSqmJoins(Consumer<SqmJoin<T, ?>> consumer) {
|
||||
if ( joins != null ) {
|
||||
|
|
|
@ -45,7 +45,7 @@ public abstract class AbstractSqmSelectQuery<T>
|
|||
implements SqmSelectQuery<T> {
|
||||
private final Map<String, SqmCteStatement<?>> cteStatements;
|
||||
private SqmQueryPart<T> sqmQueryPart;
|
||||
private Class<T> resultType;
|
||||
private final Class<T> resultType;
|
||||
|
||||
public AbstractSqmSelectQuery(Class<T> resultType, NodeBuilder builder) {
|
||||
this( new SqmQuerySpec<>( builder ), resultType, builder );
|
||||
|
@ -202,8 +202,11 @@ public abstract class AbstractSqmSelectQuery<T>
|
|||
return resultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't use this method. It has no effect.
|
||||
*/
|
||||
protected void setResultType(Class<T> resultType) {
|
||||
this.resultType = resultType;
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -410,7 +413,6 @@ public abstract class AbstractSqmSelectQuery<T>
|
|||
break;
|
||||
}
|
||||
default: {
|
||||
setResultType( (Class<T>) Object[].class );
|
||||
resultSelection = ( Selection<? extends T> ) nodeBuilder().array( selections );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,9 @@ import org.hibernate.query.sqm.tree.expression.SqmStar;
|
|||
import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.from.SqmFromClause;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
|
||||
import static org.hibernate.query.sqm.tree.SqmCopyContext.noParamCopyContext;
|
||||
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
|
||||
|
||||
/**
|
||||
|
@ -119,6 +121,10 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
|
|||
if ( existing != null ) {
|
||||
return existing;
|
||||
}
|
||||
return createCopy( context, getResultType() );
|
||||
}
|
||||
|
||||
private <X> SqmSelectStatement<X> createCopy(SqmCopyContext context, Class<X> resultType) {
|
||||
final Set<SqmParameter<?>> parameters;
|
||||
if ( this.parameters == null ) {
|
||||
parameters = null;
|
||||
|
@ -129,17 +135,19 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
|
|||
parameters.add( parameter.copy( context ) );
|
||||
}
|
||||
}
|
||||
final SqmSelectStatement<T> statement = context.registerCopy(
|
||||
//noinspection unchecked
|
||||
final SqmSelectStatement<X> statement = (SqmSelectStatement<X>) context.registerCopy(
|
||||
this,
|
||||
new SqmSelectStatement<>(
|
||||
nodeBuilder(),
|
||||
copyCteStatements( context ),
|
||||
getResultType(),
|
||||
resultType,
|
||||
getQuerySource(),
|
||||
parameters
|
||||
)
|
||||
);
|
||||
statement.setQueryPart( getQueryPart().copy( context ) );
|
||||
//noinspection unchecked
|
||||
statement.setQueryPart( (SqmQueryPart<X>) getQueryPart().copy( context ) );
|
||||
return statement;
|
||||
}
|
||||
|
||||
|
@ -266,9 +274,6 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
|
|||
checkSelectionIsJpaCompliant( selection );
|
||||
}
|
||||
getQuerySpec().setSelection( (JpaSelection<T>) selection );
|
||||
if ( getResultType() == Object.class ) {
|
||||
setResultType( (Class<T>) selection.getJavaType() );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -309,7 +314,6 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
|
|||
break;
|
||||
}
|
||||
default: {
|
||||
setResultType( (Class<T>) Object[].class );
|
||||
resultSelection = ( Selection<? extends T> ) nodeBuilder().array( selections );
|
||||
}
|
||||
}
|
||||
|
@ -460,49 +464,43 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public JpaCriteriaQuery<Long> createCountQuery() {
|
||||
final SqmCopyContext context = new NoParamSqmCopyContext() {
|
||||
@Override
|
||||
public boolean copyFetchedFlag() {
|
||||
return false;
|
||||
public SqmSelectStatement<Long> createCountQuery() {
|
||||
final SqmSelectStatement<?> copy = createCopy( noParamCopyContext(), Object.class );
|
||||
final SqmQueryPart<?> queryPart = copy.getQueryPart();
|
||||
final SqmQuerySpec<?> querySpec;
|
||||
//TODO: detect queries with no 'group by', but aggregate functions
|
||||
// in 'select' list (we don't even need to hit the database to
|
||||
// know they return exactly one row)
|
||||
if ( queryPart.isSimpleQueryPart()
|
||||
&& !( querySpec = (SqmQuerySpec<?>) queryPart ).isDistinct()
|
||||
&& querySpec.getGroupingExpressions().isEmpty() ) {
|
||||
for ( SqmRoot<?> root : querySpec.getRootList() ) {
|
||||
root.removeLeftFetchJoins();
|
||||
}
|
||||
};
|
||||
final NodeBuilder nodeBuilder = nodeBuilder();
|
||||
final Set<SqmParameter<?>> parameters;
|
||||
if ( this.parameters == null ) {
|
||||
parameters = null;
|
||||
querySpec.getSelectClause().setSelection( nodeBuilder().count( new SqmStar( nodeBuilder() )) );
|
||||
if ( querySpec.getFetch() == null && querySpec.getOffset() == null ) {
|
||||
querySpec.setOrderByClause( null );
|
||||
}
|
||||
|
||||
return (SqmSelectStatement<Long>) copy;
|
||||
}
|
||||
else {
|
||||
parameters = new LinkedHashSet<>( this.parameters.size() );
|
||||
for ( SqmParameter<?> parameter : this.parameters ) {
|
||||
parameters.add( parameter.copy( context ) );
|
||||
final JpaSelection<?> selection = queryPart.getFirstQuerySpec().getSelection();
|
||||
if ( selection.isCompoundSelection() ) {
|
||||
char c = 'a';
|
||||
for ( JpaSelection<?> item : selection.getSelectionItems() ) {
|
||||
item.alias( Character.toString( ++c ) + '_' );
|
||||
}
|
||||
}
|
||||
else {
|
||||
selection.alias( "a_" );
|
||||
}
|
||||
final SqmSubQuery<?> subquery = new SqmSubQuery<>( copy, queryPart, null, nodeBuilder() );
|
||||
final SqmSelectStatement<Long> query = nodeBuilder().createQuery( Long.class );
|
||||
query.from( subquery );
|
||||
query.select( nodeBuilder().count( new SqmStar(nodeBuilder())) );
|
||||
return query;
|
||||
}
|
||||
final SqmSelectStatement<Long> selectStatement = new SqmSelectStatement<>(
|
||||
nodeBuilder,
|
||||
copyCteStatements( context ),
|
||||
Long.class,
|
||||
SqmQuerySource.CRITERIA,
|
||||
parameters
|
||||
);
|
||||
final SqmQuerySpec<Long> querySpec = new SqmQuerySpec<>( nodeBuilder );
|
||||
|
||||
final SqmSubQuery<Tuple> subquery = new SqmSubQuery<>( selectStatement, Tuple.class, nodeBuilder );
|
||||
final SqmQueryPart<T> queryPart = getQueryPart().copy( context );
|
||||
resetSelections( queryPart );
|
||||
// Reset the
|
||||
if ( queryPart.getFetch() == null && queryPart.getOffset() == null ) {
|
||||
queryPart.setOrderByClause( null );
|
||||
}
|
||||
//noinspection unchecked
|
||||
subquery.setQueryPart( (SqmQueryPart<Tuple>) queryPart );
|
||||
|
||||
querySpec.setFromClause( new SqmFromClause( 1 ) );
|
||||
querySpec.setSelectClause( new SqmSelectClause( false, 1, nodeBuilder ) );
|
||||
selectStatement.setQueryPart( querySpec );
|
||||
selectStatement.select( nodeBuilder.count( new SqmStar( nodeBuilder ) ) );
|
||||
selectStatement.from( subquery );
|
||||
return selectStatement;
|
||||
}
|
||||
|
||||
private void resetSelections(SqmQueryPart<?> queryPart) {
|
||||
|
|
|
@ -258,7 +258,6 @@ public class SqmSubQuery<T> extends AbstractSqmSelectQuery<T> implements SqmSele
|
|||
break;
|
||||
}
|
||||
default: {
|
||||
setResultType( (Class<T>) Object[].class );
|
||||
resultSelection = ( Selection<? extends T> ) nodeBuilder().array( selections );
|
||||
}
|
||||
}
|
||||
|
@ -609,9 +608,6 @@ public class SqmSubQuery<T> extends AbstractSqmSelectQuery<T> implements SqmSele
|
|||
public void applyInferableType(SqmExpressible<?> type) {
|
||||
//noinspection unchecked
|
||||
expressibleType = (SqmExpressible<T>) type;
|
||||
if ( expressibleType != null && expressibleType.getExpressibleJavaType() != null ) {
|
||||
setResultType( expressibleType.getExpressibleJavaType().getJavaTypeClass() );
|
||||
}
|
||||
}
|
||||
|
||||
private void applyInferableType(Class<T> type) {
|
||||
|
|
|
@ -28,19 +28,49 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.SequenceGenerator;
|
||||
import jakarta.persistence.Tuple;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
@DomainModel(standardModels = StandardDomainModel.CONTACTS)
|
||||
@DomainModel(
|
||||
standardModels = StandardDomainModel.CONTACTS,
|
||||
annotatedClasses = {CountQueryTests.LogSupport.class, CountQueryTests.Contract.class}
|
||||
)
|
||||
@SessionFactory
|
||||
@JiraKey("HHH-17410")
|
||||
public class CountQueryTests {
|
||||
|
||||
@Test
|
||||
@JiraKey( "HHH-17967" )
|
||||
public void testForHHH17967(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
JpaCriteriaQuery<Contract> cq = cb.createQuery( Contract.class );
|
||||
Root<Contract> root = cq.from( Contract.class );
|
||||
cq.select( root );
|
||||
TypedQuery<Long> query = session.createQuery( cq.createCountQuery() );
|
||||
query.getSingleResult();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17410")
|
||||
public void testBasic(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -58,6 +88,7 @@ public class CountQueryTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17410")
|
||||
public void testFetches(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -66,7 +97,7 @@ public class CountQueryTests {
|
|||
"select e from Contact e join fetch e.alternativeContact",
|
||||
Contact.class
|
||||
) );
|
||||
verifyCollectionCount( session, cb.createQuery(
|
||||
verifyCount( session, cb.createQuery(
|
||||
"select e from Contact e left join fetch e.addresses",
|
||||
Contact.class
|
||||
) );
|
||||
|
@ -75,6 +106,7 @@ public class CountQueryTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17410")
|
||||
public void testConstructor(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -89,6 +121,7 @@ public class CountQueryTests {
|
|||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsRecursiveCtes.class)
|
||||
@JiraKey("HHH-17410")
|
||||
public void testCte(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -107,6 +140,7 @@ public class CountQueryTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17410")
|
||||
public void testValues(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -126,6 +160,7 @@ public class CountQueryTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17410")
|
||||
public void testParameters(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -134,7 +169,7 @@ public class CountQueryTests {
|
|||
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
|
||||
final JpaRoot<Contact> root = cq.from( Contact.class );
|
||||
final JpaParameterExpression<Contact.Gender> parameter = cb.parameter( Contact.Gender.class );
|
||||
|
||||
|
||||
cq.multiselect( root.get( "id" ), root.get( "name" ) );
|
||||
cq.where( root.get( "gender" ).equalTo( parameter ) );
|
||||
final Long count = session.createQuery( cq.createCountQuery() )
|
||||
|
@ -232,19 +267,6 @@ public class CountQueryTests {
|
|||
assertEquals( resultList.size(), count.intValue() );
|
||||
}
|
||||
|
||||
private <T> void verifyCollectionCount(SessionImplementor session, JpaCriteriaQuery<Contact> query) {
|
||||
final List<Contact> resultList = session.createQuery( query ).getResultList();
|
||||
final Long count = session.createQuery( query.createCountQuery() ).getSingleResult();
|
||||
int ormSize = 0;
|
||||
for ( Contact contact : resultList ) {
|
||||
ormSize++;
|
||||
ormSize += Math.max( contact.getAddresses().size() - 1, 0 );
|
||||
ormSize += Math.max( contact.getPhoneNumbers().size() - 1, 0 );
|
||||
}
|
||||
|
||||
assertEquals( ormSize, count.intValue() );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
|
@ -252,4 +274,23 @@ public class CountQueryTests {
|
|||
session.createMutationQuery( "delete Contact" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
@MappedSuperclass
|
||||
public static abstract class LogSupport {
|
||||
@Column(name = "SOMESTRING")
|
||||
private String s;
|
||||
}
|
||||
|
||||
@Entity
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
public static class Contract extends LogSupport {
|
||||
@Id
|
||||
@Column(name = "PK")
|
||||
@SequenceGenerator(name = "CONTRACT_GENERATOR", sequenceName = "CONTRACT_SEQ", allocationSize = 1)
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CONTRACT_GENERATOR")
|
||||
private Long syntheticId;
|
||||
@Column(name = "CUSTOMER_NAME")
|
||||
private String customerName;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue