Skip to content

Commit 9fa4973

Browse files
committed
fix: assign trailing LIMIT and OFFSET to set operations
Signed-off-by: 付典 <fudianchn@gmail.com>
1 parent bb55bb9 commit 9fa4973

3 files changed

Lines changed: 214 additions & 6 deletions

File tree

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7964,10 +7964,14 @@ Select SetOperationList(Select select) #SetOperationList: {
79647964
PlainSelect ps = (PlainSelect)selects.get(selects.size()-1);
79657965
if (ps.getOrderByElements() != null) {
79667966
list.setOrderByElements(ps.getOrderByElements());
7967-
list.setLimit(ps.getLimit());
7968-
list.setOffset(ps.getOffset());
79697967
ps.setOrderByElements(null);
7968+
}
7969+
if (ps.getLimit() != null) {
7970+
list.setLimit(ps.getLimit());
79707971
ps.setLimit(null);
7972+
}
7973+
if (ps.getOffset() != null) {
7974+
list.setOffset(ps.getOffset());
79717975
ps.setOffset(null);
79727976
}
79737977
if (ps.getInterpolate() != null) {

‎src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,8 @@ public void testUnion() throws JSQLParserException {
10041004
assertEquals("mytable2",
10051005
((Table) ((PlainSelect) setList.getSelects().get(2)).getFromItem()).getName());
10061006
assertEquals(3,
1007-
((LongValue) setList.getSelects().get(2).getLimit().getOffset()).getValue());
1007+
((LongValue) setList.getLimit().getOffset()).getValue());
1008+
assertNull(setList.getSelects().get(2).getLimit());
10081009

10091010

10101011
// with fetch and with ur
@@ -1042,11 +1043,13 @@ public void testUnion2() throws JSQLParserException {
10421043
assertEquals("mytable2",
10431044
((Table) ((PlainSelect) setList.getSelects().get(2)).getFromItem()).getName());
10441045
assertEquals(3,
1045-
((LongValue) setList.getSelects().get(2).getLimit().getRowCount())
1046+
((LongValue) setList.getLimit().getRowCount())
10461047
.getValue());
1047-
assertNull(setList.getSelects().get(2).getLimit().getOffset());
1048+
assertNull(setList.getLimit().getOffset());
10481049
assertEquals(new LongValue(4),
1049-
setList.getSelects().get(2).getOffset().getOffset());
1050+
setList.getOffset().getOffset());
1051+
assertNull(setList.getSelects().get(2).getLimit());
1052+
assertNull(setList.getSelects().get(2).getOffset());
10501053

10511054
}
10521055

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.select;
11+
12+
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
13+
import static org.junit.jupiter.api.Assertions.*;
14+
15+
import java.sql.Connection;
16+
import java.sql.DriverManager;
17+
import java.sql.ResultSet;
18+
import java.util.stream.Stream;
19+
import net.sf.jsqlparser.JSQLParserException;
20+
import net.sf.jsqlparser.expression.LongValue;
21+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.params.ParameterizedTest;
24+
import org.junit.jupiter.params.provider.Arguments;
25+
import org.junit.jupiter.params.provider.MethodSource;
26+
import org.junit.jupiter.params.provider.ValueSource;
27+
28+
class SetOperationLimitTest {
29+
30+
@ParameterizedTest
31+
@MethodSource("globalLimitCases")
32+
void trailingLimitBelongsToSetOperation(String sql, String rowCount, String limitOffset,
33+
String offset) throws JSQLParserException {
34+
SetOperationList select = (SetOperationList) assertSqlCanBeParsedAndDeparsed(sql);
35+
assertNotNull(select.getLimit(), sql);
36+
assertEquals(rowCount, select.getLimit().getRowCount().toString());
37+
assertEquals(limitOffset, select.getLimit().getOffset() == null ? null
38+
: select.getLimit().getOffset().toString());
39+
assertEquals(offset, select.getOffset() == null ? null
40+
: select.getOffset().getOffset().toString());
41+
Select last = select.getSelects().get(select.getSelects().size() - 1);
42+
assertNull(last.getLimit());
43+
assertNull(last.getOffset());
44+
if (sql.contains("ORDER BY")) {
45+
assertNotNull(select.getOrderByElements());
46+
assertEquals(1, select.getOrderByElements().size());
47+
assertNull(last.getOrderByElements());
48+
}
49+
}
50+
51+
static Stream<Arguments> globalLimitCases() {
52+
return Stream.of(
53+
Arguments.of("SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT 1", "1", null, null),
54+
Arguments.of("SELECT a FROM t1 UNION ALL SELECT b FROM t2 LIMIT 1", "1", null,
55+
null),
56+
Arguments.of("SELECT a FROM t1 INTERSECT SELECT b FROM t2 LIMIT 1", "1", null,
57+
null),
58+
Arguments.of("SELECT a FROM t1 EXCEPT SELECT b FROM t2 LIMIT 1", "1", null, null),
59+
Arguments.of("SELECT a FROM t1 MINUS SELECT b FROM t2 LIMIT 1", "1", null, null),
60+
Arguments.of("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 LIMIT 1", "1", null,
61+
null),
62+
Arguments.of("WITH t AS (SELECT 1 AS a) SELECT a FROM t UNION SELECT 2 LIMIT 1",
63+
"1", null, null),
64+
Arguments.of("SELECT a FROM t1 UNION (SELECT b FROM t2) LIMIT 1", "1", null, null),
65+
Arguments.of("SELECT a FROM t1 UNION SELECT b FROM t2 ORDER BY b LIMIT 1", "1",
66+
null, null),
67+
Arguments.of(
68+
"SELECT id FROM table1 UNION SELECT id FROM table2 ORDER BY id ASC LIMIT 55",
69+
"55", null, null),
70+
Arguments.of(
71+
"SELECT * FROM table1 UNION SELECT * FROM table2 ORDER BY col LIMIT 4 OFFSET 5",
72+
"4", null, "5"),
73+
Arguments.of("SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT (1 + 2)", "(1 + 2)",
74+
null, null),
75+
Arguments.of("SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT ? OFFSET ?", "?", null,
76+
"?"),
77+
Arguments.of("SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT :cap OFFSET :skip",
78+
":cap", null, ":skip"),
79+
Arguments.of("SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT ALL OFFSET 2", "ALL",
80+
null, "2"),
81+
Arguments.of("SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT NULL", "NULL", null,
82+
null),
83+
Arguments.of("SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT 2, 3", "3", "2", null),
84+
Arguments.of("SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT 3 OFFSET 2", "3", null,
85+
"2"));
86+
}
87+
88+
@ParameterizedTest
89+
@ValueSource(strings = {
90+
"SELECT a FROM t1 UNION SELECT b FROM t2 OFFSET 2",
91+
"SELECT a FROM t1 UNION SELECT b FROM t2 OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY",
92+
"SELECT a FROM t1 UNION SELECT b FROM t2 ORDER BY b OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY"
93+
})
94+
void trailingOffsetBelongsToSetOperation(String sql) throws JSQLParserException {
95+
SetOperationList select = (SetOperationList) assertSqlCanBeParsedAndDeparsed(sql);
96+
assertNotNull(select.getOffset());
97+
assertEquals(new LongValue(2), select.getOffset().getOffset());
98+
assertNull(select.getSelects().get(1).getOffset());
99+
if (sql.contains("FETCH")) {
100+
assertEquals(1, select.getFetch().getRowCount());
101+
assertNull(select.getSelects().get(1).getFetch());
102+
}
103+
}
104+
105+
@Test
106+
void fetchWithoutOffsetStillBelongsToSetOperation() throws JSQLParserException {
107+
SetOperationList select = (SetOperationList) assertSqlCanBeParsedAndDeparsed(
108+
"SELECT a FROM t1 UNION SELECT b FROM t2 FETCH FIRST 1 ROWS ONLY");
109+
assertEquals(1, select.getFetch().getRowCount());
110+
assertNull(select.getSelects().get(1).getFetch());
111+
}
112+
113+
@Test
114+
void plainSelectKeepsItsLimitAndOffset() throws JSQLParserException {
115+
PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(
116+
"SELECT a FROM t LIMIT 1 OFFSET 2");
117+
assertEquals(new LongValue(1), select.getLimit().getRowCount());
118+
assertEquals(new LongValue(2), select.getOffset().getOffset());
119+
}
120+
121+
@ParameterizedTest
122+
@ValueSource(strings = {"LIMIT 1", "LIMIT 1 OFFSET 2", "ORDER BY b LIMIT 1 OFFSET 2",
123+
"OFFSET 2 ROWS FETCH FIRST 1 ROWS ONLY"})
124+
void parenthesizedBranchKeepsItsOwnClauses(String clauses) throws JSQLParserException {
125+
SetOperationList select = (SetOperationList) assertSqlCanBeParsedAndDeparsed(
126+
"SELECT a FROM t1 UNION (SELECT b FROM t2 " + clauses + ")");
127+
assertNull(select.getLimit());
128+
assertNull(select.getOffset());
129+
assertNull(select.getFetch());
130+
assertNull(select.getOrderByElements());
131+
PlainSelect branch = ((ParenthesedSelect) select.getSelects().get(1)).getPlainSelect();
132+
assertEquals("SELECT b FROM t2 " + clauses, branch.toString());
133+
if (clauses.contains("LIMIT")) {
134+
assertEquals(new LongValue(1), branch.getLimit().getRowCount());
135+
}
136+
if (clauses.contains("OFFSET")) {
137+
assertEquals(new LongValue(2), branch.getOffset().getOffset());
138+
}
139+
if (clauses.contains("FETCH")) {
140+
assertEquals(1, branch.getFetch().getRowCount());
141+
}
142+
}
143+
144+
@Test
145+
void branchAndSetOperationCanBothHaveLimits() throws JSQLParserException {
146+
SetOperationList select = (SetOperationList) assertSqlCanBeParsedAndDeparsed(
147+
"(SELECT 1 LIMIT 1) UNION ALL (SELECT 2 LIMIT 2 OFFSET 1) LIMIT 3 OFFSET 4");
148+
assertEquals(new LongValue(3), select.getLimit().getRowCount());
149+
assertEquals(new LongValue(4), select.getOffset().getOffset());
150+
assertEquals(new LongValue(1),
151+
((ParenthesedSelect) select.getSelects().get(0)).getSelect().getLimit()
152+
.getRowCount());
153+
Select last = ((ParenthesedSelect) select.getSelects().get(1)).getSelect();
154+
assertEquals(new LongValue(2), last.getLimit().getRowCount());
155+
assertEquals(new LongValue(1), last.getOffset().getOffset());
156+
}
157+
158+
@Test
159+
void nestedSetOperationLimitsStayInTheirOwnScope() throws JSQLParserException {
160+
SetOperationList select = (SetOperationList) assertSqlCanBeParsedAndDeparsed(
161+
"SELECT 1 UNION ALL (SELECT 2 UNION ALL SELECT 3 LIMIT 1) LIMIT 2");
162+
assertEquals(new LongValue(2), select.getLimit().getRowCount());
163+
SetOperationList inner =
164+
((ParenthesedSelect) select.getSelects().get(1)).getSetOperationList();
165+
assertEquals(new LongValue(1), inner.getLimit().getRowCount());
166+
assertNull(inner.getSelects().get(1).getLimit());
167+
}
168+
169+
@Test
170+
void rewritingGlobalLimitChangesWholeResult() throws Exception {
171+
SetOperationList select = (SetOperationList) CCJSqlParserUtil.parse(
172+
"SELECT 1 UNION ALL SELECT 2 LIMIT 1");
173+
assertEquals(1, rowCount(select.toString()));
174+
assertNotNull(select.getLimit());
175+
select.getLimit().setRowCount(new LongValue(0));
176+
assertEquals(0, rowCount(select.toString()));
177+
}
178+
179+
@Test
180+
void rewritingLocalLimitKeepsOtherBranchRows() throws Exception {
181+
SetOperationList select = (SetOperationList) CCJSqlParserUtil.parse(
182+
"SELECT 1 UNION ALL (SELECT 2 LIMIT 1)");
183+
assertEquals(2, rowCount(select.toString()));
184+
Select branch = ((ParenthesedSelect) select.getSelects().get(1)).getSelect();
185+
branch.getLimit().setRowCount(new LongValue(0));
186+
assertNull(select.getLimit());
187+
assertEquals(1, rowCount(select.toString()));
188+
}
189+
190+
private static int rowCount(String sql) throws Exception {
191+
try (Connection connection = DriverManager.getConnection("jdbc:h2:mem:");
192+
java.sql.Statement statement = connection.createStatement();
193+
ResultSet resultSet = statement.executeQuery(sql)) {
194+
int count = 0;
195+
while (resultSet.next()) {
196+
count++;
197+
}
198+
return count;
199+
}
200+
}
201+
}

0 commit comments

Comments
 (0)