Skip to content

Commit 43710af

Browse files
committed
fix: support window frame exclusions
1 parent 24cf1bd commit 43710af

3 files changed

Lines changed: 115 additions & 5 deletions

File tree

src/main/java/net/sf/jsqlparser/expression/WindowElement.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class WindowElement implements Serializable {
1717
private Type type;
1818
private WindowOffset offset;
1919
private WindowRange range;
20+
private Exclusion exclusion;
2021

2122
public Type getType() {
2223
return type;
@@ -42,6 +43,14 @@ public void setRange(WindowRange range) {
4243
this.range = range;
4344
}
4445

46+
public Exclusion getExclusion() {
47+
return exclusion;
48+
}
49+
50+
public void setExclusion(Exclusion exclusion) {
51+
this.exclusion = exclusion;
52+
}
53+
4554
@Override
4655
public String toString() {
4756
StringBuilder buffer = new StringBuilder(type.toString());
@@ -52,6 +61,10 @@ public String toString() {
5261
buffer.append(range.toString());
5362
}
5463

64+
if (exclusion != null) {
65+
buffer.append(" EXCLUDE ").append(exclusion);
66+
}
67+
5568
return buffer.toString();
5669
}
5770

@@ -70,6 +83,11 @@ public WindowElement withRange(WindowRange range) {
7083
return this;
7184
}
7285

86+
public WindowElement withExclusion(Exclusion exclusion) {
87+
this.setExclusion(exclusion);
88+
return this;
89+
}
90+
7391
public enum Type {
7492
ROWS, RANGE, GROUPS;
7593

@@ -78,4 +96,19 @@ public static Type from(String type) {
7896
}
7997
}
8098

99+
public enum Exclusion {
100+
CURRENT_ROW("CURRENT ROW"), GROUP("GROUP"), TIES("TIES"), NO_OTHERS("NO OTHERS");
101+
102+
private final String keyword;
103+
104+
Exclusion(String keyword) {
105+
this.keyword = keyword;
106+
}
107+
108+
@Override
109+
public String toString() {
110+
return keyword;
111+
}
112+
}
113+
81114
}

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1157,7 +1157,6 @@ String NonReservedWord() :
11571157
| tk=<K_FUNCTION:"FUNCTION">
11581158
| tk=<K_GRANT:"GRANT">
11591159
| tk=<K_GROUP_CONCAT:"GROUP_CONCAT">
1160-
| tk=<K_GROUPS:"GROUPS">
11611160
| tk=<K_GUARD:"GUARD">
11621161
| tk=<K_HASH:"HASH">
11631162
| tk=<K_HIGH : "HIGH">
@@ -1444,6 +1443,7 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */
14441443
| <K_GLOBAL:"GLOBAL">
14451444
| <K_GROUP:"GROUP">
14461445
| <K_GROUPING:"GROUPING">
1446+
| <K_GROUPS:"GROUPS">
14471447
| <K_HAVING:"HAVING">
14481448
| <K_IF:"IF">
14491449
| <K_IIF:"IIF">
@@ -9204,6 +9204,7 @@ WindowElement WindowElement():
92049204
WindowElement windowElement = new WindowElement();
92059205
WindowRange range = new WindowRange();
92069206
WindowOffset offset = null;
9207+
WindowElement.Exclusion exclusion = null;
92079208
}
92089209
{
92099210
(<K_ROWS> { windowElement.setType(WindowElement.Type.ROWS); }
@@ -9217,12 +9218,44 @@ WindowElement WindowElement():
92179218
|
92189219
offset = WindowOffset() { windowElement.setOffset(offset); }
92199220
)
9221+
[ exclusion = FrameExclusion() { windowElement.setExclusion(exclusion); } ]
92209222

92219223
{
92229224
return windowElement;
92239225
}
92249226
}
92259227

9228+
WindowElement.Exclusion FrameExclusion():
9229+
{
9230+
WindowElement.Exclusion exclusion = null;
9231+
}
9232+
{
9233+
<K_EXCLUDE>
9234+
(
9235+
<K_CURRENT> <K_ROW>
9236+
{ exclusion = WindowElement.Exclusion.CURRENT_ROW; }
9237+
|
9238+
<K_GROUP>
9239+
{ exclusion = WindowElement.Exclusion.GROUP; }
9240+
|
9241+
LOOKAHEAD({
9242+
getToken(1).kind == S_IDENTIFIER
9243+
&& getToken(1).image.equalsIgnoreCase("TIES")
9244+
})
9245+
<S_IDENTIFIER>
9246+
{ exclusion = WindowElement.Exclusion.TIES; }
9247+
|
9248+
<K_NO>
9249+
LOOKAHEAD({
9250+
getToken(1).kind == S_IDENTIFIER
9251+
&& getToken(1).image.equalsIgnoreCase("OTHERS")
9252+
})
9253+
<S_IDENTIFIER>
9254+
{ exclusion = WindowElement.Exclusion.NO_OTHERS; }
9255+
)
9256+
{ return exclusion; }
9257+
}
9258+
92269259
WindowOffset WindowOffset():
92279260
{
92289261
WindowOffset offset = new WindowOffset();

src/test/java/net/sf/jsqlparser/statement/select/WindowFunctionTest.java

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
package net.sf.jsqlparser.statement.select;
1111

1212
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
14+
import static org.junit.jupiter.api.Assertions.assertNotNull;
1315

1416
import net.sf.jsqlparser.JSQLParserException;
1517
import net.sf.jsqlparser.expression.AnalyticExpression;
18+
import net.sf.jsqlparser.expression.Expression;
1619
import net.sf.jsqlparser.expression.WindowElement;
1720
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
1821
import net.sf.jsqlparser.test.TestUtils;
@@ -50,14 +53,43 @@ public void testWindowFrameGroupsIssue2431() throws JSQLParserException {
5053
"SELECT SUM(value) OVER (ORDER BY ts "
5154
+ "GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM events";
5255

53-
PlainSelect plainSelect = (PlainSelect) CCJSqlParserUtil.parse(sqlString);
54-
AnalyticExpression analyticExpression =
55-
plainSelect.getSelectItem(0).getExpression(AnalyticExpression.class);
56+
WindowElement windowElement = parseWindowElement(sqlString, 0);
57+
assertEquals(WindowElement.Type.GROUPS, windowElement.getType());
58+
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true);
59+
}
5660

57-
assertEquals(WindowElement.Type.GROUPS, analyticExpression.getWindowElement().getType());
61+
@Test
62+
public void testWindowFrameGroupsExcludeTiesIssue2431() throws JSQLParserException {
63+
String sqlString =
64+
"SELECT id, ts, value, SUM(value) OVER (ORDER BY ts "
65+
+ "GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE TIES) AS sum_excl_ties "
66+
+ "FROM events ORDER BY ts, id";
67+
68+
WindowElement windowElement = parseWindowElement(sqlString, 3);
69+
assertEquals(WindowElement.Exclusion.TIES, windowElement.getExclusion());
5870
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true);
5971
}
6072

73+
@Test
74+
public void testWindowFrameExclusionsIssue2431() throws JSQLParserException {
75+
String[] sqlStrings = {
76+
"SELECT SUM(value) OVER (ORDER BY ts ROWS UNBOUNDED PRECEDING EXCLUDE CURRENT ROW) FROM events",
77+
"SELECT SUM(value) OVER (ORDER BY ts RANGE CURRENT ROW EXCLUDE GROUP) FROM events",
78+
"SELECT SUM(value) OVER (ORDER BY ts GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE TIES) FROM events",
79+
"SELECT SUM(value) OVER (ORDER BY ts GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) FROM events"
80+
};
81+
82+
for (String sqlString : sqlStrings) {
83+
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true);
84+
}
85+
}
86+
87+
@Test
88+
public void testFrameExclusionIdentifierCompatibilityIssue2431() throws JSQLParserException {
89+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT ties FROM ties", true);
90+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT others FROM others", true);
91+
}
92+
6193
@Test
6294
public void testWindowFrameGroupsVariantsIssue2431() throws JSQLParserException {
6395
String singleSidedSqlString =
@@ -69,4 +101,16 @@ public void testWindowFrameGroupsVariantsIssue2431() throws JSQLParserException
69101
TestUtils.assertSqlCanBeParsedAndDeparsed(singleSidedSqlString, true);
70102
TestUtils.assertSqlCanBeParsedAndDeparsed(namedWindowSqlString, true);
71103
}
104+
105+
private WindowElement parseWindowElement(String sqlString, int selectItemIndex)
106+
throws JSQLParserException {
107+
PlainSelect plainSelect = (PlainSelect) CCJSqlParserUtil.parse(sqlString);
108+
Expression expression = plainSelect.getSelectItem(selectItemIndex).getExpression();
109+
assertInstanceOf(AnalyticExpression.class, expression);
110+
AnalyticExpression analyticExpression = (AnalyticExpression) expression;
111+
WindowElement windowElement = analyticExpression.getWindowElement();
112+
113+
assertNotNull(windowElement);
114+
return windowElement;
115+
}
72116
}

0 commit comments

Comments
 (0)