Skip to content

Commit 7b0eebf

Browse files
committed
Support HQL LIMIT/OFFSET without ordering.
Closes #3882
1 parent c6d5a13 commit 7b0eebf

File tree

5 files changed

+55
-48
lines changed

5 files changed

+55
-48
lines changed

spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ cteAttributes
8585
;
8686

8787
orderedQuery
88-
: (query | '(' queryExpression ')') queryOrder?
88+
: (query | '(' queryExpression ')') queryOrder? limitClause? offsetClause? fetchClause?
8989
;
9090

9191
query
@@ -94,7 +94,7 @@ query
9494
;
9595

9696
queryOrder
97-
: orderByClause limitClause? offsetClause? fetchClause?
97+
: orderByClause
9898
;
9999

100100
fromClause

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717

1818
import static org.springframework.data.jpa.repository.query.QueryTokens.*;
1919

20-
import org.springframework.data.jpa.repository.query.HqlParser.SelectClauseContext;
21-
2220
import org.jspecify.annotations.Nullable;
21+
22+
import org.springframework.data.jpa.repository.query.HqlParser.SelectClauseContext;
2323
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
2424
import org.springframework.data.jpa.repository.query.QueryTransformers.CountSelectionTokenStream;
2525

@@ -62,8 +62,16 @@ public QueryRendererBuilder visitOrderedQuery(HqlParser.OrderedQueryContext ctx)
6262
builder.appendExpression(nested);
6363
}
6464

65-
if (ctx.queryOrder() != null) {
66-
builder.append(visit(ctx.queryOrder()));
65+
if (ctx.limitClause() != null) {
66+
builder.appendExpression(visit(ctx.limitClause()));
67+
}
68+
69+
if (ctx.offsetClause() != null) {
70+
builder.appendExpression(visit(ctx.offsetClause()));
71+
}
72+
73+
if (ctx.fetchClause() != null) {
74+
builder.appendExpression(visit(ctx.fetchClause()));
6775
}
6876

6977
return builder;
@@ -237,26 +245,6 @@ public QueryTokenStream visitSelection(HqlParser.SelectionContext ctx) {
237245
return builder;
238246
}
239247

240-
@Override
241-
public QueryRendererBuilder visitQueryOrder(HqlParser.QueryOrderContext ctx) {
242-
243-
QueryRendererBuilder builder = QueryRenderer.builder();
244-
245-
if (ctx.limitClause() != null) {
246-
builder.appendExpression(visit(ctx.limitClause()));
247-
}
248-
249-
if (ctx.offsetClause() != null) {
250-
builder.appendExpression(visit(ctx.offsetClause()));
251-
}
252-
253-
if (ctx.fetchClause() != null) {
254-
builder.appendExpression(visit(ctx.fetchClause()));
255-
}
256-
257-
return builder;
258-
}
259-
260248
private QueryRendererBuilder visitSubQuerySelectClause(SelectClauseContext ctx, QueryRendererBuilder builder) {
261249

262250
if (ctx.DISTINCT() != null) {

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,18 @@ public QueryTokenStream visitOrderedQuery(HqlParser.OrderedQueryContext ctx) {
239239
builder.append(visit(ctx.queryOrder()));
240240
}
241241

242+
if (ctx.limitClause() != null) {
243+
builder.appendExpression(visit(ctx.limitClause()));
244+
}
245+
246+
if (ctx.offsetClause() != null) {
247+
builder.appendExpression(visit(ctx.offsetClause()));
248+
}
249+
250+
if (ctx.fetchClause() != null) {
251+
builder.appendExpression(visit(ctx.fetchClause()));
252+
}
253+
242254
return builder;
243255
}
244256

@@ -298,26 +310,7 @@ public QueryTokenStream visitFromQuery(HqlParser.FromQueryContext ctx) {
298310

299311
@Override
300312
public QueryTokenStream visitQueryOrder(HqlParser.QueryOrderContext ctx) {
301-
302-
if (ctx.limitClause() == null && ctx.offsetClause() == null && ctx.fetchClause() == null) {
303-
return visit(ctx.orderByClause());
304-
}
305-
306-
QueryRendererBuilder builder = QueryRenderer.builder();
307-
308-
builder.appendExpression(visit(ctx.orderByClause()));
309-
310-
if (ctx.limitClause() != null) {
311-
builder.appendExpression(visit(ctx.limitClause()));
312-
}
313-
if (ctx.offsetClause() != null) {
314-
builder.appendExpression(visit(ctx.offsetClause()));
315-
}
316-
if (ctx.fetchClause() != null) {
317-
builder.appendExpression(visit(ctx.fetchClause()));
318-
}
319-
320-
return builder;
313+
return visit(ctx.orderByClause());
321314
}
322315

323316
@Override

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919

2020
import java.util.List;
2121

22-
import org.springframework.data.domain.Sort;
23-
2422
import org.jspecify.annotations.Nullable;
23+
24+
import org.springframework.data.domain.Sort;
2525
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
2626
import org.springframework.data.repository.query.ReturnedType;
2727
import org.springframework.util.Assert;
@@ -180,6 +180,18 @@ private QueryRendererBuilder visitOrderedQuery(HqlParser.OrderedQueryContext ctx
180180
}
181181
}
182182

183+
if (ctx.limitClause() != null) {
184+
builder.appendExpression(visit(ctx.limitClause()));
185+
}
186+
187+
if (ctx.offsetClause() != null) {
188+
builder.appendExpression(visit(ctx.offsetClause()));
189+
}
190+
191+
if (ctx.fetchClause() != null) {
192+
builder.appendExpression(visit(ctx.fetchClause()));
193+
}
194+
183195
return builder;
184196
}
185197

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,6 +2044,20 @@ void orderByWithNullsFirstOrLastShouldWork() {
20442044
""");
20452045
}
20462046

2047+
@Test // GH-3882
2048+
void shouldSupportLimitOffset() {
2049+
2050+
assertQuery("SELECT si from StockItem si order by si.id LIMIT 10 OFFSET 10 FETCH FIRST 10 ROWS ONLY");
2051+
assertQuery("SELECT si from StockItem si order by si.id LIMIT ? OFFSET ? FETCH FIRST ? ROWS ONLY");
2052+
assertQuery("SELECT si from StockItem si order by si.id LIMIT :l OFFSET :o");
2053+
assertQuery("SELECT si from StockItem si LIMIT :l OFFSET :o");
2054+
assertQuery("SELECT si from StockItem si order by si.id LIMIT :l");
2055+
assertQuery("SELECT si from StockItem si order by si.id OFFSET 1");
2056+
assertQuery("SELECT si from StockItem si LIMIT 1");
2057+
assertQuery("SELECT si from StockItem si OFFSET 1");
2058+
assertQuery("SELECT si from StockItem si FETCH FIRST 1 ROWS ONLY");
2059+
}
2060+
20472061
@Test // GH-2964
20482062
void roundFunctionShouldWorkLikeAnyOtherFunction() {
20492063

0 commit comments

Comments
 (0)