Skip to content

Commit ec6e3f3

Browse files
authored
Insert the TOP clause in the correct position (#1077)
1 parent bcc3e12 commit ec6e3f3

File tree

12 files changed

+156
-3
lines changed

12 files changed

+156
-3
lines changed

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

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package org.seasar.doma.internal.jdbc.dialect;
22

3+
import java.util.Optional;
4+
import org.seasar.doma.internal.jdbc.sql.SimpleSqlNodeVisitor;
35
import org.seasar.doma.internal.jdbc.sql.node.AnonymousNode;
6+
import org.seasar.doma.internal.jdbc.sql.node.DistinctNode;
47
import org.seasar.doma.internal.jdbc.sql.node.FragmentNode;
58
import org.seasar.doma.internal.jdbc.sql.node.SelectClauseNode;
69
import org.seasar.doma.internal.jdbc.sql.node.SelectStatementNode;
710
import org.seasar.doma.jdbc.SqlNode;
11+
import org.seasar.doma.jdbc.SqlNodeVisitor;
812

913
public class Mssql2008PagingTransformer extends StandardPagingTransformer {
1014

@@ -36,11 +40,29 @@ public SqlNode visitSelectStatementNode(SelectStatementNode node, Void p) {
3640

3741
protected SqlNode appendTopNode(SelectStatementNode node) {
3842
SelectClauseNode select = new SelectClauseNode(node.getSelectClauseNode().getWordNode());
39-
select.appendNode(new FragmentNode(" top (" + limit + ")"));
40-
for (SqlNode child : node.getSelectClauseNode().getChildren()) {
41-
select.appendNode(child);
43+
FragmentNode top = new FragmentNode(" top (" + limit + ")");
44+
Optional<SqlNode> optionalDistinctNode = getDistinctNode(node.getSelectClauseNode());
45+
46+
if (optionalDistinctNode.isPresent()) {
47+
SqlNode distinctNode = optionalDistinctNode.get();
48+
for (SqlNode child : node.getSelectClauseNode().getChildren()) {
49+
select.appendNode(child);
50+
if (child == distinctNode) {
51+
select.appendNode(top);
52+
}
53+
}
54+
} else {
55+
select.appendNode(top);
56+
for (SqlNode child : node.getSelectClauseNode().getChildren()) {
57+
select.appendNode(child);
58+
}
4259
}
4360

61+
return createSelectStatementNode(node, select);
62+
}
63+
64+
private static SelectStatementNode createSelectStatementNode(
65+
SelectStatementNode node, SelectClauseNode select) {
4466
SelectStatementNode result = new SelectStatementNode();
4567
result.setSelectClauseNode(select);
4668
result.setFromClauseNode(node.getFromClauseNode());
@@ -52,4 +74,22 @@ protected SqlNode appendTopNode(SelectStatementNode node) {
5274
result.setOptionClauseNode(node.getOptionClauseNode());
5375
return result;
5476
}
77+
78+
private Optional<SqlNode> getDistinctNode(SelectClauseNode node) {
79+
SqlNodeVisitor<Boolean, Void> visitor =
80+
new SimpleSqlNodeVisitor<Boolean, Void>() {
81+
82+
@Override
83+
protected Boolean defaultAction(SqlNode node, Void o) {
84+
return false;
85+
}
86+
87+
@Override
88+
public Boolean visitDistinctNode(DistinctNode node, Void o) {
89+
return true;
90+
}
91+
};
92+
93+
return node.getChildren().stream().filter(child -> child.accept(visitor, null)).findFirst();
94+
}
5595
}

doma-core/src/main/java/org/seasar/doma/internal/jdbc/sql/NodePreparedSqlBuilder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.seasar.doma.internal.jdbc.sql.node.AnonymousNode;
2727
import org.seasar.doma.internal.jdbc.sql.node.BindVariableNode;
2828
import org.seasar.doma.internal.jdbc.sql.node.CommentNode;
29+
import org.seasar.doma.internal.jdbc.sql.node.DistinctNode;
2930
import org.seasar.doma.internal.jdbc.sql.node.ElseNode;
3031
import org.seasar.doma.internal.jdbc.sql.node.ElseifNode;
3132
import org.seasar.doma.internal.jdbc.sql.node.EmbeddedVariableNode;
@@ -688,6 +689,12 @@ public Void visitWordNode(WordNode node, Context p) {
688689
return null;
689690
}
690691

692+
@Override
693+
public Void visitDistinctNode(DistinctNode node, Context context) {
694+
WordNode wordNode = node.getWordNode();
695+
return wordNode.accept(this, context);
696+
}
697+
691698
@Override
692699
public Void visitFragmentNode(FragmentNode node, Context p) {
693700
p.setAvailable(true);

doma-core/src/main/java/org/seasar/doma/internal/jdbc/sql/SimpleSqlNodeVisitor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.seasar.doma.internal.jdbc.sql.node.AnonymousNode;
44
import org.seasar.doma.internal.jdbc.sql.node.BindVariableNode;
55
import org.seasar.doma.internal.jdbc.sql.node.CommentNode;
6+
import org.seasar.doma.internal.jdbc.sql.node.DistinctNode;
67
import org.seasar.doma.internal.jdbc.sql.node.ElseNode;
78
import org.seasar.doma.internal.jdbc.sql.node.ElseifNode;
89
import org.seasar.doma.internal.jdbc.sql.node.EmbeddedVariableNode;
@@ -57,6 +58,11 @@ public R visitCommentNode(CommentNode node, P p) {
5758
return defaultAction(node, p);
5859
}
5960

61+
@Override
62+
public R visitDistinctNode(DistinctNode node, P p) {
63+
return defaultAction(node, p);
64+
}
65+
6066
@Override
6167
public R visitElseifNode(ElseifNode node, P p) {
6268
return defaultAction(node, p);

doma-core/src/main/java/org/seasar/doma/internal/jdbc/sql/SqlParser.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.seasar.doma.internal.jdbc.sql.node.BindVariableNode;
1313
import org.seasar.doma.internal.jdbc.sql.node.BlockNode;
1414
import org.seasar.doma.internal.jdbc.sql.node.CommentNode;
15+
import org.seasar.doma.internal.jdbc.sql.node.DistinctNode;
1516
import org.seasar.doma.internal.jdbc.sql.node.ElseNode;
1617
import org.seasar.doma.internal.jdbc.sql.node.ElseifNode;
1718
import org.seasar.doma.internal.jdbc.sql.node.EmbeddedVariableNode;
@@ -131,6 +132,11 @@ public SqlNode parse() {
131132
parseOptionWord();
132133
break;
133134
}
135+
case DISTINCT_WORD:
136+
{
137+
parseDistinctWord();
138+
break;
139+
}
134140
case UPDATE_WORD:
135141
{
136142
parseUpdateWord();
@@ -400,6 +406,11 @@ protected void parseWord() {
400406
appendNode(node);
401407
}
402408

409+
protected void parseDistinctWord() {
410+
DistinctNode node = new DistinctNode(token);
411+
appendNode(node);
412+
}
413+
403414
protected void parseComment() {
404415
CommentNode node = new CommentNode(token);
405416
appendNode(node);

doma-core/src/main/java/org/seasar/doma/internal/jdbc/sql/SqlTokenType.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ public String extract(String token) {
141141

142142
WORD,
143143

144+
DISTINCT_WORD,
145+
144146
OTHER,
145147

146148
WHITESPACE,

doma-core/src/main/java/org/seasar/doma/internal/jdbc/sql/SqlTokenizer.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static org.seasar.doma.internal.jdbc.sql.SqlTokenType.BLOCK_COMMENT;
66
import static org.seasar.doma.internal.jdbc.sql.SqlTokenType.CLOSED_PARENS;
77
import static org.seasar.doma.internal.jdbc.sql.SqlTokenType.DELIMITER;
8+
import static org.seasar.doma.internal.jdbc.sql.SqlTokenType.DISTINCT_WORD;
89
import static org.seasar.doma.internal.jdbc.sql.SqlTokenType.ELSEIF_BLOCK_COMMENT;
910
import static org.seasar.doma.internal.jdbc.sql.SqlTokenType.ELSE_BLOCK_COMMENT;
1011
import static org.seasar.doma.internal.jdbc.sql.SqlTokenType.EMBEDDED_VARIABLE_BLOCK_COMMENT;
@@ -243,6 +244,18 @@ protected void peekEightChars(
243244
type = OPTION_WORD;
244245
buf.position(buf.position() - 2);
245246
return;
247+
} else if ((c == 'd' || c == 'D')
248+
&& (c2 == 'i' || c2 == 'I')
249+
&& (c3 == 's' || c3 == 'S')
250+
&& (c4 == 't' || c4 == 'T')
251+
&& (c5 == 'i' || c5 == 'I')
252+
&& (c6 == 'n' || c6 == 'N')
253+
&& (c7 == 'c' || c7 == 'C')
254+
&& (c8 == 't' || c8 == 'T')) {
255+
type = DISTINCT_WORD;
256+
if (isWordTerminated()) {
257+
return;
258+
}
246259
}
247260
buf.position(buf.position() - 1);
248261
peekSevenChars(c, c2, c3, c4, c5, c6, c7);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.seasar.doma.internal.jdbc.sql.node;
2+
3+
import org.seasar.doma.DomaNullPointerException;
4+
import org.seasar.doma.jdbc.JdbcUnsupportedOperationException;
5+
import org.seasar.doma.jdbc.SqlNode;
6+
import org.seasar.doma.jdbc.SqlNodeVisitor;
7+
8+
public class DistinctNode extends AbstractSqlNode {
9+
10+
protected final WordNode wordNode;
11+
12+
public DistinctNode(String word) {
13+
wordNode = new WordNode(word, false);
14+
}
15+
16+
public WordNode getWordNode() {
17+
return wordNode;
18+
}
19+
20+
@Override
21+
public void appendNode(SqlNode child) {
22+
throw new JdbcUnsupportedOperationException(getClass().getName(), "addNode");
23+
}
24+
25+
@Override
26+
public <R, P> R accept(SqlNodeVisitor<R, P> visitor, P p) {
27+
if (visitor == null) {
28+
throw new DomaNullPointerException("visitor");
29+
}
30+
return visitor.visitDistinctNode(this, p);
31+
}
32+
}

doma-core/src/main/java/org/seasar/doma/jdbc/SqlNodeVisitor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public interface SqlNodeVisitor<R, P> {
1818

1919
R visitCommentNode(CommentNode node, P p);
2020

21+
R visitDistinctNode(DistinctNode node, P p);
22+
2123
R visitElseifNode(ElseifNode node, P p);
2224

2325
R visitElseNode(ElseNode node, P p);

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,28 @@ public void testLimitOnly() {
5050
PreparedSql sql = sqlBuilder.build(sqlNode, Function.identity());
5151
assertEquals(expected, sql.getRawSql());
5252
}
53+
54+
@Test
55+
public void testLimitOnly_distinct() {
56+
String expected = "select distinct top (10) emp.id from emp order by emp.id";
57+
Mssql2008PagingTransformer transformer = new Mssql2008PagingTransformer(-1, 10);
58+
SqlParser parser = new SqlParser("select distinct emp.id from emp order by emp.id");
59+
SqlNode sqlNode = transformer.transform(parser.parse());
60+
NodePreparedSqlBuilder sqlBuilder =
61+
new NodePreparedSqlBuilder(new MockConfig(), SqlKind.SELECT, "dummyPath");
62+
PreparedSql sql = sqlBuilder.build(sqlNode, Function.identity());
63+
assertEquals(expected, sql.getRawSql());
64+
}
65+
66+
@Test
67+
public void testLimitOnly_countDistinct() {
68+
String expected = "select top (10) count(distinct emp.id) from emp order by emp.id";
69+
Mssql2008PagingTransformer transformer = new Mssql2008PagingTransformer(-1, 10);
70+
SqlParser parser = new SqlParser("select count(distinct emp.id) from emp order by emp.id");
71+
SqlNode sqlNode = transformer.transform(parser.parse());
72+
NodePreparedSqlBuilder sqlBuilder =
73+
new NodePreparedSqlBuilder(new MockConfig(), SqlKind.SELECT, "dummyPath");
74+
PreparedSql sql = sqlBuilder.build(sqlNode, Function.identity());
75+
assertEquals(expected, sql.getRawSql());
76+
}
5377
}

integration-test-java/src/main/java/org/seasar/doma/it/dao/EmployeeDao.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ public interface EmployeeDao {
6969
@Select
7070
List<Employee> selectAll(SelectOptions options);
7171

72+
@Select
73+
List<Employee> selectDistinctAll(SelectOptions options);
74+
7275
@Select(ensureResultMapping = true)
7376
List<Employee> selectOnlyNameWithMappingCheck();
7477

0 commit comments

Comments
 (0)