Skip to content

Commit b4658d9

Browse files
committed
Add support for fetch first paging queries
1 parent 6e651cc commit b4658d9

File tree

8 files changed

+459
-10
lines changed

8 files changed

+459
-10
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Copyright 2016-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.select;
17+
18+
import java.util.Optional;
19+
20+
public class FetchFirstPagingModel implements PagingModel {
21+
22+
private Long fetchFirstRows;
23+
private Long offset;
24+
25+
private FetchFirstPagingModel(Builder builder) {
26+
this.fetchFirstRows = builder.fetchFirstRows;
27+
this.offset = builder.offset;
28+
}
29+
30+
public Optional<Long> fetchFirstRows() {
31+
return Optional.ofNullable(fetchFirstRows);
32+
}
33+
34+
public Optional<Long> offset() {
35+
return Optional.ofNullable(offset);
36+
}
37+
38+
@Override
39+
public <R> R accept(PagingModelVisitor<R> visitor) {
40+
return visitor.visit(this);
41+
}
42+
43+
public static class Builder {
44+
private Long fetchFirstRows;
45+
private Long offset;
46+
47+
public Builder withFetchFirstRows(Long fetchFirstRows) {
48+
this.fetchFirstRows = fetchFirstRows;
49+
return this;
50+
}
51+
52+
public Builder withOffset(Long offset) {
53+
this.offset = offset;
54+
return this;
55+
}
56+
57+
public FetchFirstPagingModel build() {
58+
return new FetchFirstPagingModel(this);
59+
}
60+
}
61+
}

src/main/java/org/mybatis/dynamic/sql/select/PagingModelVisitor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@
1717

1818
public interface PagingModelVisitor<R> {
1919
R visit(LimitAndOffsetPagingModel pagingModel);
20+
21+
R visit(FetchFirstPagingModel pagingModel);
2022
}

src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,16 @@ public SelectDSL<R>.LimitFinisher limit(long limit) {
149149
return selectDSL.limit(limit);
150150
}
151151

152-
public SelectDSL<R>.OffsetFinisher offset(long offset) {
152+
public SelectDSL<R>.OffsetFirstFinisher offset(long offset) {
153153
selectDSL.addQueryExpression(buildModel());
154154
return selectDSL.offset(offset);
155155
}
156156

157+
public SelectDSL<R>.FetchFirstFinisher fetchFirst(long fetchFirstRows) {
158+
selectDSL.addQueryExpression(buildModel());
159+
return selectDSL.fetchFirst(fetchFirstRows);
160+
}
161+
157162
public static class FromGatherer<R> {
158163
private FromGathererBuilder<R> builder;
159164
private Map<SqlTable, String> tableAliasMap = new HashMap<>();
@@ -250,12 +255,18 @@ public SelectDSL<R>.LimitFinisher limit(long limit) {
250255
return selectDSL.limit(limit);
251256
}
252257

253-
public SelectDSL<R>.OffsetFinisher offset(long offset) {
258+
public SelectDSL<R>.OffsetFirstFinisher offset(long offset) {
254259
whereModel = buildWhereModel();
255260
selectDSL.addQueryExpression(buildModel());
256261
return selectDSL.offset(offset);
257262
}
258263

264+
public SelectDSL<R>.FetchFirstFinisher fetchFirst(long fetchFirstRows) {
265+
whereModel = buildWhereModel();
266+
selectDSL.addQueryExpression(buildModel());
267+
return selectDSL.fetchFirst(fetchFirstRows);
268+
}
269+
259270
@Override
260271
public R build() {
261272
whereModel = buildWhereModel();
@@ -413,7 +424,7 @@ public SelectDSL<R>.LimitFinisher limit(long limit) {
413424
return selectDSL.limit(limit);
414425
}
415426

416-
public SelectDSL<R>.OffsetFinisher offset(long offset) {
427+
public SelectDSL<R>.OffsetFirstFinisher offset(long offset) {
417428
joinModel = buildJoinModel();
418429
selectDSL.addQueryExpression(buildModel());
419430
return selectDSL.offset(offset);
@@ -435,7 +446,7 @@ public SelectDSL<R>.LimitFinisher limit(long limit) {
435446
return selectDSL.limit(limit);
436447
}
437448

438-
public SelectDSL<R>.OffsetFinisher offset(long offset) {
449+
public SelectDSL<R>.OffsetFirstFinisher offset(long offset) {
439450
return selectDSL.offset(offset);
440451
}
441452
}

src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,12 @@ public LimitFinisher limit(long limit) {
102102
return new LimitFinisher(limit);
103103
}
104104

105-
public OffsetFinisher offset(long offset) {
106-
return new OffsetFinisher(offset);
105+
public OffsetFirstFinisher offset(long offset) {
106+
return new OffsetFirstFinisher(offset);
107+
}
108+
109+
public FetchFirstFinisher fetchFirst(long fetchFirstRows) {
110+
return new FetchFirstFinisher(fetchFirstRows);
107111
}
108112

109113
@Override
@@ -145,15 +149,66 @@ public OffsetFinisher(long limit, long offset) {
145149
.build();
146150
}
147151

148-
public OffsetFinisher(long offset) {
149-
pagingModel = new LimitAndOffsetPagingModel.Builder()
152+
@Override
153+
public R build() {
154+
SelectDSL.this.pagingModel = pagingModel;
155+
return SelectDSL.this.build();
156+
}
157+
}
158+
159+
public class OffsetFirstFinisher implements Buildable<R> {
160+
private long offset;
161+
162+
public OffsetFirstFinisher(long offset) {
163+
this.offset = offset;
164+
}
165+
166+
public FetchFirstFinisher fetchFirst(long fetchFirstRows) {
167+
return new FetchFirstFinisher(offset, fetchFirstRows);
168+
}
169+
170+
@Override
171+
public R build() {
172+
SelectDSL.this.pagingModel = new LimitAndOffsetPagingModel.Builder()
150173
.withOffset(offset)
151174
.build();
175+
return SelectDSL.this.build();
176+
}
177+
}
178+
179+
public class FetchFirstFinisher {
180+
private Long offset;
181+
private Long fetchFirstRows;
182+
183+
public FetchFirstFinisher(long fetchFirstRows) {
184+
this.fetchFirstRows = fetchFirstRows;
185+
}
186+
187+
public FetchFirstFinisher(long offset, long fetchFirstRows) {
188+
this.offset = offset;
189+
this.fetchFirstRows = fetchFirstRows;
190+
}
191+
192+
public RowsOnlyFinisher rowsOnly() {
193+
return new RowsOnlyFinisher(offset, fetchFirstRows);
194+
}
195+
}
196+
197+
public class RowsOnlyFinisher implements Buildable<R> {
198+
private Long offset;
199+
private Long fetchFirstRows;
200+
201+
public RowsOnlyFinisher(Long offset, Long fetchFirstRows) {
202+
this.offset = offset;
203+
this.fetchFirstRows = fetchFirstRows;
152204
}
153205

154206
@Override
155207
public R build() {
156-
SelectDSL.this.pagingModel = pagingModel;
208+
SelectDSL.this.pagingModel = new FetchFirstPagingModel.Builder()
209+
.withOffset(offset)
210+
.withFetchFirstRows(fetchFirstRows)
211+
.build();
157212
return SelectDSL.this.build();
158213
}
159214
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* Copyright 2016-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.select.render;
17+
18+
import java.util.Optional;
19+
20+
import org.mybatis.dynamic.sql.render.RenderingStrategy;
21+
import org.mybatis.dynamic.sql.select.FetchFirstPagingModel;
22+
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
23+
24+
public class FetchFirstPagingModelRenderer {
25+
private static final String FETCH_FIRST_ROWS_PARAMETER = "_fetchFirstRows"; //$NON-NLS-1$
26+
private static final String OFFSET_PARAMETER = "_offset"; //$NON-NLS-1$
27+
private RenderingStrategy renderingStrategy;
28+
private FetchFirstPagingModel pagingModel;
29+
30+
public FetchFirstPagingModelRenderer(RenderingStrategy renderingStrategy,
31+
FetchFirstPagingModel pagingModel) {
32+
this.renderingStrategy = renderingStrategy;
33+
this.pagingModel = pagingModel;
34+
}
35+
36+
public Optional<FragmentAndParameters> render() {
37+
return pagingModel.offset()
38+
.map(this::renderWithOffset)
39+
.orElse(renderFetchFirstRowsOnly());
40+
}
41+
42+
private Optional<FragmentAndParameters> renderWithOffset(Long offset) {
43+
return pagingModel.fetchFirstRows()
44+
.map(ffr -> renderOffsetAndFetchFirstRows(offset, ffr))
45+
.orElse(renderOffsetOnly(offset));
46+
}
47+
48+
private Optional<FragmentAndParameters> renderFetchFirstRowsOnly() {
49+
return pagingModel.fetchFirstRows().flatMap(this::renderFetchFirstRowsOnly);
50+
}
51+
52+
private Optional<FragmentAndParameters> renderFetchFirstRowsOnly(Long fetchFirstRows) {
53+
return FragmentAndParameters
54+
.withFragment("fetch first " + renderPlaceholder(FETCH_FIRST_ROWS_PARAMETER) //$NON-NLS-1$
55+
+ " rows only") //$NON-NLS-1$
56+
.withParameter(FETCH_FIRST_ROWS_PARAMETER, fetchFirstRows)
57+
.buildOptional();
58+
}
59+
60+
private Optional<FragmentAndParameters> renderOffsetOnly(Long offset) {
61+
return FragmentAndParameters.withFragment("offset " + renderPlaceholder(OFFSET_PARAMETER)) //$NON-NLS-1$
62+
.withParameter(OFFSET_PARAMETER, offset)
63+
.buildOptional();
64+
}
65+
66+
private Optional<FragmentAndParameters> renderOffsetAndFetchFirstRows(Long offset, Long fetchFirstRows) {
67+
return FragmentAndParameters.withFragment("offset " + renderPlaceholder(OFFSET_PARAMETER) //$NON-NLS-1$
68+
+ " fetch first " + renderPlaceholder(FETCH_FIRST_ROWS_PARAMETER) //$NON-NLS-1$
69+
+ " rows only") //$NON-NLS-1$
70+
.withParameter(OFFSET_PARAMETER, offset)
71+
.withParameter(FETCH_FIRST_ROWS_PARAMETER, fetchFirstRows)
72+
.buildOptional();
73+
}
74+
75+
private String renderPlaceholder(String parameterName) {
76+
return renderingStrategy.getFormattedJdbcPlaceholder(RenderingStrategy.DEFAULT_PARAMETER_PREFIX,
77+
parameterName);
78+
}
79+
}

src/main/java/org/mybatis/dynamic/sql/select/render/PagingModelRenderer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Optional;
1919

2020
import org.mybatis.dynamic.sql.render.RenderingStrategy;
21+
import org.mybatis.dynamic.sql.select.FetchFirstPagingModel;
2122
import org.mybatis.dynamic.sql.select.LimitAndOffsetPagingModel;
2223
import org.mybatis.dynamic.sql.select.PagingModelVisitor;
2324
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
@@ -33,4 +34,9 @@ public PagingModelRenderer(RenderingStrategy renderingStrategy) {
3334
public Optional<FragmentAndParameters> visit(LimitAndOffsetPagingModel pagingModel) {
3435
return new LimitAndOffsetPagingModelRenderer(renderingStrategy, pagingModel).render();
3536
}
37+
38+
@Override
39+
public Optional<FragmentAndParameters> visit(FetchFirstPagingModel pagingModel) {
40+
return new FetchFirstPagingModelRenderer(renderingStrategy, pagingModel).render();
41+
}
3642
}

src/site/markdown/docs/select.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,43 @@ When using a column function (lower, upper, etc.), then is is customary to give
196196
In this example the `substring` function is used in both the select list and the GROUP BY expression. In the ORDER BY expression, we use the `sortColumn` function to duplicate the alias given to the column in the select list.
197197

198198
## Limit and Offset Support
199-
Since version 1.1.1, the select statement supports limit and offset. You can specify:
199+
Since version 1.1.1 the select statement supports limit and offset for paging (or slicing) queries. You can specify:
200200

201201
- Limit only
202202
- Offset only
203203
- Both limit and offset
204204

205205
It is important to note that the select renderer writes limit and offset clauses into the generated select statement as is. The library does not attempt to normalize those values for databases that don't support limit and offset directly. Therefore, it is very important for users to understand whether or not the target database supports limit and offset. If the target database does not support limit and offset, then it is likely that using this support will create SQL that has runtime errors.
206+
207+
An example follows:
208+
209+
```java
210+
SelectStatementProvider selectStatement = select(animalData.allColumns())
211+
.from(animalData)
212+
.orderBy(id)
213+
.limit(3)
214+
.offset(22)
215+
.build()
216+
.render(RenderingStrategy.MYBATIS3);
217+
```
218+
219+
## Fetch First Support
220+
Since version 1.1.2 the select statement supports fetch first for paging (or slicing) queries. You can specify:
221+
222+
- Fetch first only
223+
- Offset only
224+
- Both offset and fetch first
225+
226+
Fetch first is an SQL standard and is supported by most databases.
227+
228+
An example follows:
229+
230+
```java
231+
SelectStatementProvider selectStatement = select(animalData.allColumns())
232+
.from(animalData)
233+
.orderBy(id)
234+
.offset(22)
235+
.fetchFirst(3).rowsOnly()
236+
.build()
237+
.render(RenderingStrategy.MYBATIS3);
238+
```

0 commit comments

Comments
 (0)