Skip to content

Commit f958fd7

Browse files
committed
Merge pull request #61 from OdaShinsuke/mssqlpaging2
SQL Server 2012 から追加された OFFSET-FETCH をページング処理に使う
2 parents 76a92f7 + d0c455c commit f958fd7

File tree

6 files changed

+206
-11
lines changed

6 files changed

+206
-11
lines changed

docs/sources/query/select.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ Iterableを使ったIN句へのマッピング
285285
| Mssql2008Dialect | offsetを指定する場合、ORDER BY句を持ちORDER BY句で指定する |
286286
| | カラムすべてをSELECT句に含んでいる |
287287
+------------------+---------------------------------------------------------------+
288+
| MssqlDialect | offsetを指定する場合、ORDER BY句を持つ必要があります |
289+
+------------------+---------------------------------------------------------------+
288290
| StandardDialect | ORDER BY句を持ちORDER BY句で指定する |
289291
| | カラムすべてをSELECT句に含んでいる |
290292
+------------------+---------------------------------------------------------------+
@@ -441,4 +443,3 @@ SQL のログ出力形式
441443
List<Employee> selectById(Integer id);
442444
443445
``SqlLogType.RAW`` はバインドパラメータ(?)付きの SQL をログ出力することを表します。
444-

src/main/java/org/seasar/doma/internal/jdbc/dialect/Mssql2008PagingTransformer.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,13 @@ public SqlNode visitSelectStatementNode(SelectStatementNode node, Void p) {
5050
}
5151
processed = true;
5252

53+
return appendTopNode(node);
54+
}
55+
56+
protected SqlNode appendTopNode(SelectStatementNode node) {
5357
SelectClauseNode select = new SelectClauseNode(node
5458
.getSelectClauseNode().getWordNode());
55-
select.appendNode(new FragmentNode(" top " + limit));
59+
select.appendNode(new FragmentNode(" top (" + limit + ")"));
5660
for (SqlNode child : node.getSelectClauseNode().getChildren()) {
5761
select.appendNode(child);
5862
}
@@ -67,5 +71,4 @@ public SqlNode visitSelectStatementNode(SelectStatementNode node, Void p) {
6771
result.setForUpdateClauseNode(node.getForUpdateClauseNode());
6872
return result;
6973
}
70-
7174
}

src/main/java/org/seasar/doma/internal/jdbc/dialect/MssqlPagingTransformer.java

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,74 @@
1515
*/
1616
package org.seasar.doma.internal.jdbc.dialect;
1717

18+
import org.seasar.doma.internal.jdbc.sql.node.FragmentNode;
19+
import org.seasar.doma.internal.jdbc.sql.node.OrderByClauseNode;
20+
import org.seasar.doma.internal.jdbc.sql.node.SelectStatementNode;
21+
import org.seasar.doma.jdbc.JdbcException;
22+
import org.seasar.doma.jdbc.SqlNode;
23+
import org.seasar.doma.message.Message;
24+
1825

1926
/**
2027
* @author taedium
2128
*
2229
*/
2330
public class MssqlPagingTransformer extends Mssql2008PagingTransformer {
2431

25-
public MssqlPagingTransformer(long offset, long limit) {
32+
private boolean forceOffsetFetch;
33+
34+
public MssqlPagingTransformer(long offset, long limit, boolean forceOffsetFetch) {
2635
super(offset, limit);
36+
this.forceOffsetFetch = forceOffsetFetch;
37+
}
38+
39+
@Override
40+
public SqlNode visitSelectStatementNode(SelectStatementNode node, Void p) {
41+
if (processed) {
42+
return node;
43+
}
44+
processed = true;
45+
46+
if (!forceOffsetFetch && offset <= 0) {
47+
return super.appendTopNode(node);
48+
}
49+
50+
OrderByClauseNode originalOrderBy = node.getOrderByClauseNode();
51+
if (originalOrderBy == null) {
52+
throw new JdbcException(Message.DOMA2201);
53+
}
54+
55+
OrderByClauseNode orderBy = new OrderByClauseNode(
56+
originalOrderBy.getWordNode());
57+
58+
for (SqlNode child : originalOrderBy.getChildren()) {
59+
orderBy.appendNode(child);
60+
}
61+
62+
String offset = this.offset <= 0 ? "0" : String.valueOf(this.offset);
63+
64+
orderBy.appendNode(new FragmentNode(" offset "));
65+
orderBy.appendNode(new FragmentNode(offset));
66+
orderBy.appendNode(new FragmentNode(" rows"));
67+
if (this.limit > 0) {
68+
orderBy.appendNode(new FragmentNode(" fetch next "));
69+
orderBy.appendNode(new FragmentNode(String.valueOf(this.limit)));
70+
orderBy.appendNode(new FragmentNode(" rows only"));
71+
}
72+
73+
if (node.getForUpdateClauseNode() != null) {
74+
orderBy.appendNode(new FragmentNode(" "));
75+
}
76+
77+
SelectStatementNode result = new SelectStatementNode();
78+
result.setSelectClauseNode(node.getSelectClauseNode());
79+
result.setFromClauseNode(node.getFromClauseNode());
80+
result.setWhereClauseNode(node.getWhereClauseNode());
81+
result.setGroupByClauseNode(node.getGroupByClauseNode());
82+
result.setHavingClauseNode(node.getHavingClauseNode());
83+
result.setOrderByClauseNode(orderBy);
84+
result.setForUpdateClauseNode(node.getForUpdateClauseNode());
85+
return result;
2786
}
2887

2988
}

src/main/java/org/seasar/doma/jdbc/dialect/MssqlDialect.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@
4040
*/
4141
public class MssqlDialect extends Mssql2008Dialect {
4242

43+
private boolean pagingForceOffsetFetch;
4344
/**
4445
* インスタンスを構築します。
4546
*/
4647
public MssqlDialect() {
4748
this(new MssqlJdbcMappingVisitor(), new MssqlSqlLogFormattingVisitor(),
48-
new MssqlExpressionFunctions());
49+
new MssqlExpressionFunctions(), false);
4950
}
5051

5152
/**
@@ -56,7 +57,7 @@ public MssqlDialect() {
5657
*/
5758
public MssqlDialect(JdbcMappingVisitor jdbcMappingVisitor) {
5859
this(jdbcMappingVisitor, new MssqlSqlLogFormattingVisitor(),
59-
new MssqlExpressionFunctions());
60+
new MssqlExpressionFunctions(), false);
6061
}
6162

6263
/**
@@ -68,7 +69,7 @@ public MssqlDialect(JdbcMappingVisitor jdbcMappingVisitor) {
6869
*/
6970
public MssqlDialect(SqlLogFormattingVisitor sqlLogFormattingVisitor) {
7071
this(new MssqlJdbcMappingVisitor(), sqlLogFormattingVisitor,
71-
new MssqlExpressionFunctions());
72+
new MssqlExpressionFunctions(), false);
7273
}
7374

7475
/**
@@ -79,7 +80,7 @@ public MssqlDialect(SqlLogFormattingVisitor sqlLogFormattingVisitor) {
7980
*/
8081
public MssqlDialect(ExpressionFunctions expressionFunctions) {
8182
this(new MssqlJdbcMappingVisitor(), new MssqlSqlLogFormattingVisitor(),
82-
expressionFunctions);
83+
expressionFunctions, false);
8384
}
8485

8586
/**
@@ -95,7 +96,7 @@ public MssqlDialect(ExpressionFunctions expressionFunctions) {
9596
public MssqlDialect(JdbcMappingVisitor jdbcMappingVisitor,
9697
SqlLogFormattingVisitor sqlLogFormattingVisitor) {
9798
this(jdbcMappingVisitor, sqlLogFormattingVisitor,
98-
new MssqlExpressionFunctions());
99+
new MssqlExpressionFunctions(), false);
99100
}
100101

101102
/**
@@ -113,7 +114,29 @@ public MssqlDialect(JdbcMappingVisitor jdbcMappingVisitor,
113114
public MssqlDialect(JdbcMappingVisitor jdbcMappingVisitor,
114115
SqlLogFormattingVisitor sqlLogFormattingVisitor,
115116
ExpressionFunctions expressionFunctions) {
117+
this(jdbcMappingVisitor, sqlLogFormattingVisitor, expressionFunctions, false);
118+
}
119+
120+
/**
121+
* {@link JdbcMappingVisitor} と {@link SqlLogFormattingVisitor} と
122+
* {@link ExpressionFunctions} を指定してインスタンスを構築します。
123+
*
124+
* @param jdbcMappingVisitor
125+
* {@link Wrapper} をJDBCの型とマッピングするビジター
126+
* @param sqlLogFormattingVisitor
127+
* SQLのバインド変数にマッピングされる {@link Wrapper}
128+
* をログ用のフォーマットされた文字列へと変換するビジター
129+
* @param expressionFunctions
130+
* SQLのコメント式で利用可能な関数群
131+
* @param pagingForceOffsetFetch
132+
* ページングを行う際、常に OFFSET-FETCH で行うかどうか
133+
*/
134+
public MssqlDialect(JdbcMappingVisitor jdbcMappingVisitor,
135+
SqlLogFormattingVisitor sqlLogFormattingVisitor,
136+
ExpressionFunctions expressionFunctions,
137+
boolean pagingForceOffsetFetch) {
116138
super(jdbcMappingVisitor, sqlLogFormattingVisitor, expressionFunctions);
139+
this.pagingForceOffsetFetch = pagingForceOffsetFetch;
117140
}
118141

119142
@Override
@@ -133,7 +156,7 @@ protected SqlNode toForUpdateSqlNode(SqlNode sqlNode,
133156
@Override
134157
protected SqlNode toPagingSqlNode(SqlNode sqlNode, long offset, long limit) {
135158
MssqlPagingTransformer transformer = new MssqlPagingTransformer(offset,
136-
limit);
159+
limit, this.pagingForceOffsetFetch);
137160
return transformer.transform(sqlNode);
138161
}
139162

src/test/java/org/seasar/doma/internal/jdbc/dialect/Mssql2008PagingTransformerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public void testOffsetOnly() throws Exception {
5959
}
6060

6161
public void testLimitOnly() throws Exception {
62-
String expected = "select top 10 emp.id from emp order by emp.id";
62+
String expected = "select top (10) emp.id from emp order by emp.id";
6363
Mssql2008PagingTransformer transformer = new Mssql2008PagingTransformer(
6464
-1, 10);
6565
SqlParser parser = new SqlParser(
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2004-2010 the Seasar Foundation and the Others.
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,
13+
* either express or implied. See the License for the specific language
14+
* governing permissions and limitations under the License.
15+
*/
16+
package org.seasar.doma.internal.jdbc.dialect;
17+
18+
import java.util.function.Function;
19+
20+
import junit.framework.TestCase;
21+
22+
import org.seasar.doma.internal.jdbc.mock.MockConfig;
23+
import org.seasar.doma.internal.jdbc.sql.NodePreparedSqlBuilder;
24+
import org.seasar.doma.internal.jdbc.sql.PreparedSql;
25+
import org.seasar.doma.internal.jdbc.sql.SqlParser;
26+
import org.seasar.doma.jdbc.SqlKind;
27+
import org.seasar.doma.jdbc.SqlNode;
28+
29+
/**
30+
* @author shinsuke-oda
31+
*
32+
*/
33+
public class MssqlPagingTransformerTest extends TestCase {
34+
35+
public void testOffsetLimit() throws Exception {
36+
String expected = "select emp.id from emp order by emp.id offset 5 rows fetch next 10 rows only";
37+
MssqlPagingTransformer transformer = new MssqlPagingTransformer(
38+
5, 10, false);
39+
SqlParser parser = new SqlParser(
40+
"select emp.id from emp order by emp.id");
41+
SqlNode sqlNode = transformer.transform(parser.parse());
42+
NodePreparedSqlBuilder sqlBuilder = new NodePreparedSqlBuilder(
43+
new MockConfig(), SqlKind.SELECT, "dummyPath");
44+
PreparedSql sql = sqlBuilder.build(sqlNode, Function.identity());
45+
assertEquals(expected, sql.getRawSql());
46+
}
47+
public void testOffsetLimit_forceOffsetFetch() throws Exception {
48+
String expected = "select emp.id from emp order by emp.id offset 5 rows fetch next 10 rows only";
49+
MssqlPagingTransformer transformer = new MssqlPagingTransformer(
50+
5, 10, true);
51+
SqlParser parser = new SqlParser(
52+
"select emp.id from emp order by emp.id");
53+
SqlNode sqlNode = transformer.transform(parser.parse());
54+
NodePreparedSqlBuilder sqlBuilder = new NodePreparedSqlBuilder(
55+
new MockConfig(), SqlKind.SELECT, "dummyPath");
56+
PreparedSql sql = sqlBuilder.build(sqlNode, Function.identity());
57+
assertEquals(expected, sql.getRawSql());
58+
}
59+
60+
public void testOffsetOnly() throws Exception {
61+
String expected = "select emp.id from emp order by emp.id offset 5 rows";
62+
MssqlPagingTransformer transformer = new MssqlPagingTransformer(
63+
5, -1, false);
64+
SqlParser parser = new SqlParser(
65+
"select emp.id from emp order by emp.id");
66+
SqlNode sqlNode = transformer.transform(parser.parse());
67+
NodePreparedSqlBuilder sqlBuilder = new NodePreparedSqlBuilder(
68+
new MockConfig(), SqlKind.SELECT, "dummyPath");
69+
PreparedSql sql = sqlBuilder.build(sqlNode, Function.identity());
70+
assertEquals(expected, sql.getRawSql());
71+
}
72+
public void testOffsetOnly_forceOffsetFetch() throws Exception {
73+
String expected = "select emp.id from emp order by emp.id offset 5 rows";
74+
MssqlPagingTransformer transformer = new MssqlPagingTransformer(
75+
5, -1, true);
76+
SqlParser parser = new SqlParser(
77+
"select emp.id from emp order by emp.id");
78+
SqlNode sqlNode = transformer.transform(parser.parse());
79+
NodePreparedSqlBuilder sqlBuilder = new NodePreparedSqlBuilder(
80+
new MockConfig(), SqlKind.SELECT, "dummyPath");
81+
PreparedSql sql = sqlBuilder.build(sqlNode, Function.identity());
82+
assertEquals(expected, sql.getRawSql());
83+
}
84+
85+
public void testLimitOnly() throws Exception {
86+
String expected = "select top (10) emp.id from emp order by emp.id";
87+
MssqlPagingTransformer transformer = new MssqlPagingTransformer(
88+
-1, 10, false);
89+
SqlParser parser = new SqlParser(
90+
"select emp.id from emp order by emp.id");
91+
SqlNode sqlNode = transformer.transform(parser.parse());
92+
NodePreparedSqlBuilder sqlBuilder = new NodePreparedSqlBuilder(
93+
new MockConfig(), SqlKind.SELECT, "dummyPath");
94+
PreparedSql sql = sqlBuilder.build(sqlNode, Function.identity());
95+
assertEquals(expected, sql.getRawSql());
96+
}
97+
public void testLimitOnly_forceOffsetFetch() throws Exception {
98+
String expected = "select emp.id from emp order by emp.id offset 0 rows fetch next 10 rows only";
99+
MssqlPagingTransformer transformer = new MssqlPagingTransformer(
100+
-1, 10, true);
101+
SqlParser parser = new SqlParser(
102+
"select emp.id from emp order by emp.id");
103+
SqlNode sqlNode = transformer.transform(parser.parse());
104+
NodePreparedSqlBuilder sqlBuilder = new NodePreparedSqlBuilder(
105+
new MockConfig(), SqlKind.SELECT, "dummyPath");
106+
PreparedSql sql = sqlBuilder.build(sqlNode, Function.identity());
107+
assertEquals(expected, sql.getRawSql());
108+
}
109+
}

0 commit comments

Comments
 (0)