Skip to content

HHH-19397 allow LIMIT + OFFSET without ORDER BY (for v6.6) #10153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 6.6
Choose a base branch
from
Open
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 @@ -167,9 +167,9 @@ queryExpression
* A query with an optional 'order by' clause
*/
orderedQuery
: query queryOrder? # QuerySpecExpression
| LEFT_PAREN queryExpression RIGHT_PAREN queryOrder? # NestedQueryExpression
| queryOrder # QueryOrderExpression
: query orderByClause? limitOffset # QuerySpecExpression
| LEFT_PAREN queryExpression RIGHT_PAREN orderByClause? limitOffset # NestedQueryExpression
| orderByClause limitOffset # QueryOrderExpression
;

/**
Expand All @@ -182,10 +182,10 @@ setOperator
;

/**
* The 'order by' clause and optional subclauses for limiting and pagination
* Optional subclauses for limiting and pagination
*/
queryOrder
: orderByClause limitClause? offsetClause? fetchClause?
limitOffset
: limitClause? offsetClause? fetchClause?
;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1031,35 +1031,32 @@ public SqmQueryPart<?> visitQueryOrderExpression(HqlParser.QueryOrderExpressionC
final SqmFromClause fromClause = buildInferredFromClause(null);
sqmQuerySpec.setFromClause( fromClause );
sqmQuerySpec.setSelectClause( buildInferredSelectClause( fromClause ) );
visitQueryOrder( sqmQuerySpec, ctx.queryOrder() );
visitOrderBy( sqmQuerySpec, ctx.orderByClause() );
visitLimitOffset( sqmQuerySpec, ctx.limitOffset() );
return sqmQuerySpec;
}

@Override
public SqmQueryPart<?> visitQuerySpecExpression(HqlParser.QuerySpecExpressionContext ctx) {
final SqmQueryPart<?> queryPart = visitQuery( ctx.query() );
final HqlParser.QueryOrderContext queryOrderContext = ctx.queryOrder();
if ( queryOrderContext != null ) {
visitQueryOrder( queryPart, queryOrderContext );
}
visitOrderBy( queryPart, ctx.orderByClause() );
visitLimitOffset( queryPart, ctx.limitOffset() );
return queryPart;
}

@Override
public SqmQueryPart<?> visitNestedQueryExpression(HqlParser.NestedQueryExpressionContext ctx) {
final SqmQueryPart<?> queryPart = (SqmQueryPart<?>) ctx.queryExpression().accept( this );
final HqlParser.QueryOrderContext queryOrderContext = ctx.queryOrder();
if ( queryOrderContext != null ) {
final SqmCreationProcessingState firstProcessingState = processingStateStack.pop();
processingStateStack.push(
new SqmQueryPartCreationProcessingStateStandardImpl(
processingStateStack.getCurrent(),
firstProcessingState.getProcessingQuery(),
this
)
);
visitQueryOrder( queryPart, queryOrderContext);
}
final SqmCreationProcessingState firstProcessingState = processingStateStack.pop();
processingStateStack.push(
new SqmQueryPartCreationProcessingStateStandardImpl(
processingStateStack.getCurrent(),
firstProcessingState.getProcessingQuery(),
this
)
);
visitOrderBy( queryPart, ctx.orderByClause() );
visitLimitOffset( queryPart, ctx.limitOffset() );
return queryPart;
}

Expand Down Expand Up @@ -1178,44 +1175,36 @@ public SetOperator visitSetOperator(HqlParser.SetOperatorContext ctx) {
}
}

protected void visitQueryOrder(SqmQueryPart<?> sqmQueryPart, HqlParser.QueryOrderContext ctx) {
if ( ctx == null ) {
return;
}
final SqmOrderByClause orderByClause;
final HqlParser.OrderByClauseContext orderByClauseContext = ctx.orderByClause();
if ( orderByClauseContext != null ) {
if ( creationOptions.useStrictJpaCompliance() && processingStateStack.depth() > 1 ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.SUBQUERY_ORDER_BY
);
protected void visitLimitOffset(SqmQueryPart<?> sqmQueryPart, HqlParser.LimitOffsetContext ctx) {
if (ctx != null) {
final HqlParser.LimitClauseContext limitClauseContext = ctx.limitClause();
final HqlParser.OffsetClauseContext offsetClauseContext = ctx.offsetClause();
final HqlParser.FetchClauseContext fetchClauseContext = ctx.fetchClause();
if (limitClauseContext != null || offsetClauseContext != null || fetchClauseContext != null) {
if (getCreationOptions().useStrictJpaCompliance()) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.LIMIT_OFFSET_CLAUSE
);
}
if ( processingStateStack.depth() > 1 && sqmQueryPart.getOrderByClause() == null ) {
throw new SemanticException(
"A 'limit', 'offset', or 'fetch' clause requires an 'order by' clause when used in a subquery",
query
);
}
setOffsetFetchLimit( sqmQueryPart, limitClauseContext, offsetClauseContext, fetchClauseContext );
}

orderByClause = visitOrderByClause( orderByClauseContext );
sqmQueryPart.setOrderByClause( orderByClause );
}
else {
orderByClause = null;
}
}

final HqlParser.LimitClauseContext limitClauseContext = ctx.limitClause();
final HqlParser.OffsetClauseContext offsetClauseContext = ctx.offsetClause();
final HqlParser.FetchClauseContext fetchClauseContext = ctx.fetchClause();
if ( limitClauseContext != null || offsetClauseContext != null || fetchClauseContext != null ) {
if ( getCreationOptions().useStrictJpaCompliance() ) {
protected void visitOrderBy(SqmQueryPart<?> sqmQueryPart, HqlParser.OrderByClauseContext ctx) {
if ( ctx != null ) {
if ( creationOptions.useStrictJpaCompliance() && processingStateStack.depth() > 1 ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.LIMIT_OFFSET_CLAUSE
);
}

if ( processingStateStack.depth() > 1 && orderByClause == null ) {
throw new SemanticException(
"A 'limit', 'offset', or 'fetch' clause requires an 'order by' clause when used in a subquery",
query
StrictJpaComplianceViolation.Type.SUBQUERY_ORDER_BY
);
}

setOffsetFetchLimit(sqmQueryPart, limitClauseContext, offsetClauseContext, fetchClauseContext);
sqmQueryPart.setOrderByClause( visitOrderByClause( ctx ) );
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.query.hql;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.Test;

import java.util.UUID;

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

@Jpa(annotatedClasses = LimitOffsetTest.Sortable.class)
class LimitOffsetTest {
@Test
void testLimitOffset(EntityManagerFactoryScope scope) {
scope.inTransaction( session -> {
session.persist( new Sortable() );
session.persist( new Sortable() );
session.persist( new Sortable() );
session.persist( new Sortable() );
} );
scope.inTransaction( session -> {
assertEquals( 2, session.createQuery( "from Sortable limit 2" ).getResultList().size() );
assertEquals( 2, session.createQuery( "from Sortable offset 2" ).getResultList().size() );
assertEquals( 1, session.createQuery( "from Sortable limit 1 offset 1" ).getResultList().size() );
} );
}
@Entity(name = "Sortable")
static class Sortable {
@Id
@GeneratedValue
UUID uuid;
}
}