Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -500,4 +500,9 @@ public <X> JpaFunctionRoot<X> from(JpaSetReturningFunction<X> function) {
public JpaCriteriaQuery<Long> createCountQuery() {
return query.createCountQuery();
}

@Override
public JpaCriteriaQuery<Boolean> createExistsQuery() {
return query.createExistsQuery();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.List;
import java.util.Set;

import org.hibernate.Incubating;
import org.hibernate.query.common.FetchClauseType;

import jakarta.persistence.criteria.CriteriaQuery;
Expand Down Expand Up @@ -34,6 +35,14 @@ public interface JpaCriteriaQuery<T> extends CriteriaQuery<T>, JpaQueryableCrite
*/
JpaCriteriaQuery<Long> createCountQuery();

/**
* A query that returns {@code true} if this query has any results.
*
* @since 7.1
*/
@Incubating
JpaCriteriaQuery<Boolean> createExistsQuery();

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Limit/Offset/Fetch clause

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ Map<String, SqmCteStatement<?>> getCteStatementMap() {
return new LinkedHashMap<>( cteStatements );
}

void addCteStatements(Map<String, SqmCteStatement<?>> cteStatements) {
this.cteStatements.putAll( cteStatements );
}

@Override
public SqmCteStatement<?> getCteStatement(String cteLabel) {
return cteStatements.get( cteLabel );
Expand Down Expand Up @@ -420,14 +424,11 @@ public void appendHqlString(StringBuilder hql, SqmRenderContext context) {
protected Selection<? extends T> getResultSelection(Selection<?>[] selections) {
final Class<T> resultType = getResultType();
if ( resultType == null || resultType == Object.class ) {
switch ( selections.length ) {
case 0:
throw new IllegalArgumentException( "Empty selections passed to criteria query typed as Object" );
case 1:
return (Selection<? extends T>) selections[0];
default:
return (Selection<? extends T>) nodeBuilder().array( selections );
}
return switch ( selections.length ) {
case 0 -> throw new IllegalArgumentException( "Empty selections passed to criteria query typed as Object" );
case 1 -> (Selection<? extends T>) selections[0];
default -> (Selection<? extends T>) nodeBuilder().array( selections );
};
}
else if ( Tuple.class.isAssignableFrom( resultType ) ) {
return (Selection<? extends T>) nodeBuilder().tuple( selections );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -537,10 +537,10 @@ public SqmSelectStatement<Long> createCountQuery() {
if ( querySpec.getFetch() == null && querySpec.getOffset() == null ) {
querySpec.setOrderByClause( null );
}

return (SqmSelectStatement<Long>) copy;
}
else {
//TODO: do some deeper analysis for unions (simplify their select lists)
aliasSelections( queryPart );
final SqmSubQuery<?> subquery = new SqmSubQuery<>( copy, queryPart, null, nodeBuilder() );
final SqmSelectStatement<Long> query = nodeBuilder().createQuery( Long.class );
Expand All @@ -553,6 +553,35 @@ public SqmSelectStatement<Long> createCountQuery() {
}
}

@Override
public SqmSelectStatement<Boolean> createExistsQuery() {
final SqmSelectStatement<?> copy = createCopy( noParamCopyContext(), Object.class );
final SqmQueryPart<?> queryPart = copy.getQueryPart();
//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() ) {
final SqmQuerySpec<?> querySpec = (SqmQuerySpec<?>) queryPart;
querySpec.setDistinct( false );
if ( querySpec.getGroupingExpressions().isEmpty() ) {
for ( SqmRoot<?> root : querySpec.getRootList() ) {
root.removeLeftFetchJoins();
}
querySpec.getSelectClause().setSelection( nodeBuilder().literal( 1 ) );
}
}
//TODO: do some deeper analysis for unions (simplify their select lists)
aliasSelections( queryPart );
final SqmSubQuery<?> subquery = new SqmSubQuery<>( copy, queryPart, null, nodeBuilder() );
final SqmSelectStatement<Boolean> query = nodeBuilder().createQuery( Boolean.class );
query.select( nodeBuilder().exists( subquery ) );
if ( subquery.getFetch() == null && subquery.getOffset() == null ) {
subquery.getQueryPart().setOrderByClause( null );
}
query.addCteStatements( getCteStatementMap() );
return query;
}

private <S> void aliasSelections(SqmQueryPart<S> queryPart) {
if ( queryPart.isSimpleQueryPart() ) {
final SqmQuerySpec<S> querySpec = queryPart.getFirstQuerySpec();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.List;

import org.hibernate.annotations.Imported;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
Expand Down Expand Up @@ -43,6 +44,7 @@
import jakarta.persistence.criteria.Root;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

/**
Expand Down Expand Up @@ -72,10 +74,17 @@ public void testForHHH17967(SessionFactoryScope scope) {
JpaCriteriaQuery<Contract> cq = cb.createQuery( Contract.class );
Root<Contract> root = cq.from( Contract.class );
cq.select( root );
TypedQuery<Long> query = session.createQuery( cq.createCountQuery() );
TypedQuery<Long> countQuery = session.createQuery( cq.createCountQuery() );
try {
// Leads to NPE on pre-6.5 versions
query.getSingleResult();
countQuery.getSingleResult();
}
catch (Exception e) {
fail( e );
}
TypedQuery<Boolean> existsQuery = session.createQuery( cq.createExistsQuery() );
try {
existsQuery.getSingleResult();
}
catch (Exception e) {
fail( e );
Expand All @@ -95,10 +104,17 @@ public void testForHHH18850(SessionFactoryScope scope) {
Root<Contract> root = cq.from( Contract.class );
cq.select( root );
cq.orderBy( cb.asc( root.get( "customerName" ) ) );
TypedQuery<Long> query = session.createQuery( cq.createCountQuery() );
TypedQuery<Long> countQuery = session.createQuery( cq.createCountQuery() );
try {
// Leads to NPE on pre-6.5 versions
query.getSingleResult();
countQuery.getSingleResult();
}
catch (Exception e) {
fail( e );
}
TypedQuery<Boolean> existsQuery = session.createQuery( cq.createExistsQuery() );
try {
existsQuery.getSingleResult();
}
catch (Exception e) {
fail( e );
Expand All @@ -114,10 +130,17 @@ public void testForHHH18850(SessionFactoryScope scope) {
Root<Contract> root = cq.from( Contract.class );
cq.select( root );
cq.orderBy( cb.desc( root.get( "customerName" ) ) );
TypedQuery<Long> query = session.createQuery( cq.createCountQuery() );
TypedQuery<Long> countQuery = session.createQuery( cq.createCountQuery() );
try {
// Leads to NPE on pre-6.5 versions
query.getSingleResult();
countQuery.getSingleResult();
}
catch (Exception e) {
fail( e );
}
TypedQuery<Boolean> existsQuery = session.createQuery( cq.createExistsQuery() );
try {
existsQuery.getSingleResult();
}
catch (Exception e) {
fail( e );
Expand Down Expand Up @@ -257,8 +280,10 @@ public void testDistinctDynamicInstantiation(SessionFactoryScope scope) {
)
).distinct( true );
final Long count = session.createQuery( cq.createCountQuery() ).getSingleResult();
final Boolean exists = session.createQuery( cq.createExistsQuery() ).getSingleResult();
final List<Tuple> resultList = session.createQuery( cq ).getResultList();
assertEquals( 1L, count );
assertTrue( exists );
assertEquals( resultList.size(), count.intValue() );
} );
}
Expand All @@ -278,6 +303,10 @@ public void testUnionQuery(SessionFactoryScope scope) {
cq2.select( root2.get( "name" ).get( "first" ) ).where( cb.equal( root2.get( "id" ), 2 ) );

final JpaCriteriaQuery<String> union = cb.union( cq1, cq2 );
if ( !(scope.getSessionFactory().getJdbcServices().getDialect() instanceof SybaseDialect) ) {
final Boolean exists = session.createQuery( union.createExistsQuery() ).getSingleResult();
assertTrue( exists );
}
final Long count = session.createQuery( union.createCountQuery() ).getSingleResult();
final List<String> resultList = session.createQuery( union ).getResultList();
assertEquals( 2L, count );
Expand Down Expand Up @@ -390,16 +419,13 @@ private <T> void verifyCount(SessionImplementor session, JpaCriteriaQuery<?> que
final List<?> resultList = session.createQuery( query ).getResultList();
final Long count = session.createQuery( query.createCountQuery() ).getSingleResult();
assertEquals( resultList.size(), count.intValue() );
final Boolean exists = session.createQuery( query.createExistsQuery() ).getSingleResult();
assertEquals( !resultList.isEmpty(), exists );
}

@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createMutationQuery( "update Contact set alternativeContact = null" ).executeUpdate();
session.createMutationQuery( "delete Contact" ).executeUpdate();
session.createMutationQuery( "delete ChildEntity" ).executeUpdate();
session.createMutationQuery( "delete ParentEntity" ).executeUpdate();
} );
scope.getSessionFactory().getSchemaManager().truncate();
}

@MappedSuperclass
Expand Down
Loading