Skip to content

Commit ef91198

Browse files
committed
Support KSQL's WINDOW
Add support for KSQL's WINDOW (HOPPING, TUMBLING and SESSION window)
1 parent 6e7b976 commit ef91198

File tree

6 files changed

+276
-1
lines changed

6 files changed

+276
-1
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2019 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 net.sf.jsqlparser.parser.ASTNodeAccessImpl;
13+
14+
public class KSQLWindow extends ASTNodeAccessImpl {
15+
16+
public enum TimeUnit {
17+
DAY ("DAY"),
18+
HOUR ("HOUR"),
19+
MINUTE ("MINUTE"),
20+
SECOND ("SECOND"),
21+
MILLISECOND ("MILLISECOND"),
22+
DAYS ("DAYS"),
23+
HOURS ("HOURS"),
24+
MINUTES ("MINUTES"),
25+
SECONDS ("SECONDS"),
26+
MILLISECONDS ("MILLISECONDS");
27+
28+
private String timeUnit;
29+
30+
TimeUnit(String timeUnit) {
31+
this.timeUnit = timeUnit;
32+
}
33+
34+
public String getTimeUnit() {
35+
return timeUnit;
36+
}
37+
}
38+
39+
public enum WindowType {
40+
HOPPING ("HOPPING"),
41+
SESSION ("SESSION"),
42+
TUMBLING ("TUMBLING");
43+
44+
private String windowType;
45+
46+
WindowType(String windowType) {
47+
this.windowType = windowType;
48+
}
49+
50+
public String getWindowType() {
51+
return windowType;
52+
}
53+
}
54+
55+
private boolean hopping;
56+
private boolean tumbling;
57+
private boolean session;
58+
private long sizeDuration;
59+
private TimeUnit sizeTimeUnit;
60+
private long advanceDuration;
61+
private TimeUnit advanceTimeUnit;
62+
63+
public boolean isHoppingWindow() {
64+
return hopping;
65+
}
66+
67+
public void setHoppingWindow(boolean hopping) {
68+
this.hopping = hopping;
69+
}
70+
71+
public boolean isTumblingWindow() {
72+
return tumbling;
73+
}
74+
75+
public void setTumblingWindow(boolean tumbling) {
76+
this.tumbling = tumbling;
77+
}
78+
79+
public boolean isSessionWindow() {
80+
return session;
81+
}
82+
83+
public void setSessionWindow(boolean session) {
84+
this.session = session;
85+
}
86+
87+
public long getSizeDuration() {
88+
return sizeDuration;
89+
}
90+
91+
public void setSizeDuration(long sizeDuration) {
92+
this.sizeDuration = sizeDuration;
93+
}
94+
95+
public TimeUnit getSizeTimeUnit() {
96+
return sizeTimeUnit;
97+
}
98+
99+
public void setSizeTimeUnit(TimeUnit sizeTimeUnit) {
100+
this.sizeTimeUnit = sizeTimeUnit;
101+
}
102+
103+
public long getAdvanceDuration() {
104+
return advanceDuration;
105+
}
106+
107+
public void setAdvanceDuration(long advanceDuration) {
108+
this.advanceDuration = advanceDuration;
109+
}
110+
111+
public TimeUnit getAdvanceTimeUnit() {
112+
return advanceTimeUnit;
113+
}
114+
115+
public void setAdvanceTimeUnit(TimeUnit advanceTimeUnit) {
116+
this.advanceTimeUnit = advanceTimeUnit;
117+
}
118+
119+
public KSQLWindow() {
120+
}
121+
122+
@Override
123+
public String toString() {
124+
if (isHoppingWindow()) {
125+
return "HOPPING (" + "SIZE " + sizeDuration + " " + sizeTimeUnit + ", " +
126+
"ADVANCE BY " + advanceDuration + " " + advanceTimeUnit + ")";
127+
} else if (isSessionWindow()) {
128+
return "SESSION (" + sizeDuration + " " + sizeTimeUnit + ")";
129+
} else {
130+
return "TUMBLING (" + "SIZE " + sizeDuration + " " + sizeTimeUnit + ")";
131+
}
132+
}
133+
}

src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class PlainSelect extends ASTNodeAccessImpl implements SelectBody {
4747
private boolean mySqlSqlCalcFoundRows = false;
4848
private boolean sqlNoCacheFlag = false;
4949
private String forXmlPath;
50+
private KSQLWindow ksqlWindow = null;
5051

5152
public boolean isUseBrackets() {
5253
return useBrackets;
@@ -280,6 +281,14 @@ public void setForXmlPath(String forXmlPath) {
280281
this.forXmlPath = forXmlPath;
281282
}
282283

284+
public KSQLWindow getKsqlWindow() {
285+
return ksqlWindow;
286+
}
287+
288+
public void setKsqlWindow(KSQLWindow ksqlWindow) {
289+
this.ksqlWindow = ksqlWindow;
290+
}
291+
283292
@Override
284293
public String toString() {
285294
StringBuilder sql = new StringBuilder();
@@ -337,6 +346,10 @@ public String toString() {
337346
}
338347
}
339348
}
349+
350+
if (ksqlWindow != null) {
351+
sql.append(" WINDOW ").append(ksqlWindow.toString());
352+
}
340353
if (where != null) {
341354
sql.append(" WHERE ").append(where);
342355
}

src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ public void visit(PlainSelect plainSelect) {
114114
}
115115
}
116116

117+
if (plainSelect.getKsqlWindow() != null) {
118+
buffer.append(" WINDOW ");
119+
buffer.append(plainSelect.getKsqlWindow().toString());
120+
}
121+
117122
if (plainSelect.getWhere() != null) {
118123
buffer.append(" WHERE ");
119124
plainSelect.getWhere().accept(expressionVisitor);

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
106106
{
107107
<K_ACTION: "ACTION">
108108
| <K_ADD:"ADD">
109+
| <K_ADVANCE:"ADVANCE">
109110
| <K_ALGORITHM: "ALGORITHM">
110111
| <K_ALL:"ALL">
111112
| <K_ALTER:"ALTER">
@@ -177,6 +178,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
177178
| <K_GROUP_CONCAT:"GROUP_CONCAT">
178179
| <K_HAVING:"HAVING">
179180
| <K_HIGH_PRIORITY : "HIGH_PRIORITY">
181+
| <K_HOPPING:"HOPPING">
180182
| <K_IF:"IF">
181183
| <K_IIF:"IIF">
182184
| <K_IGNORE : "IGNORE">
@@ -248,11 +250,13 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
248250
| <K_SELECT: ("SELECT" | "SEL")>
249251
| <K_SEMI : "SEMI">
250252
| <K_SEPARATOR:"SEPARATOR">
253+
| <K_SESSION:"SESSION">
251254
| <K_SET:"SET">
252255
| <K_SETS:"SETS">
253256
| <K_SHOW : "SHOW">
254257
| <K_SIBLINGS:"SIBLINGS">
255258
| <K_SIMILAR:"SIMILAR">
259+
| <K_SIZE:"SIZE">
256260
| <K_SKIP: "SKIP">
257261
| <K_SOME:"SOME">
258262
| <K_START:"START">
@@ -266,6 +270,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
266270
| <K_TOP:"TOP">
267271
| <K_TRAILING:"TRAILING">
268272
| <K_TRUNCATE:"TRUNCATE">
273+
| <K_TUMBLING:"TUMBLING">
269274
| <K_TYPE:"TYPE">
270275
| <K_UNBOUNDED: "UNBOUNDED">
271276
| <K_UNION:"UNION">
@@ -287,6 +292,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
287292
| <K_WAIT : "WAIT">
288293
| <K_WHEN:"WHEN">
289294
| <K_WHERE:"WHERE">
295+
| <K_WINDOW:"WINDOW">
290296
| <K_WITH:"WITH">
291297
| <K_WITHIN:"WITHIN">
292298
| <K_WITHOUT:"WITHOUT">
@@ -1186,6 +1192,7 @@ PlainSelect PlainSelect() #PlainSelect:
11861192
Wait wait = null;
11871193
boolean mySqlSqlCalcFoundRows = false;
11881194
Token token;
1195+
KSQLWindow ksqlWindow=null;
11891196
}
11901197
{
11911198
<K_SELECT>
@@ -1226,6 +1233,7 @@ PlainSelect PlainSelect() #PlainSelect:
12261233
fromItem=FromItem()
12271234
joins=JoinsList() ]
12281235

1236+
[ ksqlWindow=KSQLWindowClause() { plainSelect.setKsqlWindow(ksqlWindow); } ]
12291237
[ where=WhereClause() { plainSelect.setWhere(where); }]
12301238
[ oracleHierarchicalQueryClause=OracleHierarchicalQueryClause() { plainSelect.setOracleHierarchical(oracleHierarchicalQueryClause); } ]
12311239
[ groupBy=GroupByColumnReferences() { plainSelect.setGroupByElement(groupBy); }]
@@ -1779,6 +1787,49 @@ KSQLJoinWindow JoinWindow():
17791787
})
17801788
}
17811789

1790+
KSQLWindow KSQLWindowClause():
1791+
{
1792+
KSQLWindow retval = null;
1793+
Token sizeDurationToken = null;
1794+
Token sizeTimeUnitToken = null;
1795+
Token advanceDurationToken = null;
1796+
Token advanceTimeUnitToken = null;
1797+
}
1798+
{
1799+
<K_WINDOW>
1800+
{
1801+
retval=new KSQLWindow();
1802+
retval.setHoppingWindow(false);
1803+
retval.setSessionWindow(false);
1804+
retval.setTumblingWindow(false);
1805+
}
1806+
(
1807+
<K_HOPPING> "("
1808+
<K_SIZE> sizeDurationToken=<S_LONG> sizeTimeUnitToken=<S_IDENTIFIER> ","
1809+
<K_ADVANCE> <K_BY> advanceDurationToken=<S_LONG> advanceTimeUnitToken=<S_IDENTIFIER> ")"
1810+
{
1811+
retval.setHoppingWindow(true);
1812+
} |
1813+
<K_SESSION> "(" sizeDurationToken=<S_LONG> sizeTimeUnitToken=<S_IDENTIFIER> ")"
1814+
{
1815+
retval.setSessionWindow(true);
1816+
} |
1817+
<K_TUMBLING> "(" <K_SIZE> sizeDurationToken=<S_LONG> sizeTimeUnitToken=<S_IDENTIFIER> ")"
1818+
{
1819+
retval.setTumblingWindow(true);
1820+
}
1821+
)
1822+
{
1823+
retval.setSizeDuration(Long.parseLong(sizeDurationToken.image));
1824+
retval.setSizeTimeUnit(KSQLWindow.TimeUnit.valueOf(sizeTimeUnitToken.image));
1825+
if (advanceDurationToken != null) {
1826+
retval.setAdvanceDuration(Long.parseLong(advanceDurationToken.image));
1827+
retval.setAdvanceTimeUnit(KSQLWindow.TimeUnit.valueOf(advanceTimeUnitToken.image));
1828+
}
1829+
return retval;
1830+
}
1831+
}
1832+
17821833
Expression WhereClause():
17831834
{
17841835
Expression retval = null;

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,77 @@ public void testKSQLBeforeAfterWindowedJoin() throws Exception {
8080

8181
assertSqlCanBeParsedAndDeparsed(sql, true);
8282
}
83+
84+
@Test
85+
public void testKSQLHoppingWindows() throws Exception {
86+
String sql;
87+
Statement statement;
88+
sql = "SELECT *\n"
89+
+ "FROM table1 t1\n"
90+
+ "WINDOW HOPPING (SIZE 30 SECONDS, ADVANCE BY 10 MINUTES)\n"
91+
+ "GROUP BY region.id\n";
92+
93+
statement = CCJSqlParserUtil.parse(sql);
94+
System.out.println(statement.toString());
95+
96+
Select select = (Select) statement;
97+
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
98+
assertTrue(plainSelect.getKsqlWindow().isHoppingWindow());
99+
assertFalse(plainSelect.getKsqlWindow().isSessionWindow());
100+
assertFalse(plainSelect.getKsqlWindow().isTumblingWindow());
101+
assertEquals(30L, plainSelect.getKsqlWindow().getSizeDuration());
102+
assertEquals("SECONDS", plainSelect.getKsqlWindow().getSizeTimeUnit().toString());
103+
assertEquals(10L, plainSelect.getKsqlWindow().getAdvanceDuration());
104+
assertEquals("MINUTES", plainSelect.getKsqlWindow().getAdvanceTimeUnit().toString());
105+
assertStatementCanBeDeparsedAs(select, sql, true);
106+
107+
assertSqlCanBeParsedAndDeparsed(sql, true);
108+
}
109+
110+
@Test
111+
public void testKSQLSessionWindows() throws Exception {
112+
String sql;
113+
Statement statement;
114+
sql = "SELECT *\n"
115+
+ "FROM table1 t1\n"
116+
+ "WINDOW SESSION (5 MINUTES)\n"
117+
+ "GROUP BY region.id\n";
118+
119+
statement = CCJSqlParserUtil.parse(sql);
120+
System.out.println(statement.toString());
121+
122+
Select select = (Select) statement;
123+
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
124+
assertTrue(plainSelect.getKsqlWindow().isSessionWindow());
125+
assertFalse(plainSelect.getKsqlWindow().isHoppingWindow());
126+
assertFalse(plainSelect.getKsqlWindow().isTumblingWindow());
127+
assertEquals(5L, plainSelect.getKsqlWindow().getSizeDuration());
128+
assertEquals("MINUTES", plainSelect.getKsqlWindow().getSizeTimeUnit().toString());
129+
130+
assertStatementCanBeDeparsedAs(select, sql, true);
131+
assertSqlCanBeParsedAndDeparsed(sql, true);
132+
}
133+
134+
@Test
135+
public void testKSQLTumblingWindows() throws Exception {
136+
String sql;
137+
Statement statement;
138+
sql = "SELECT *\n"
139+
+ "FROM table1 t1\n"
140+
+ "WINDOW TUMBLING (SIZE 30 SECONDS)\n"
141+
+ "GROUP BY region.id\n";
142+
143+
statement = CCJSqlParserUtil.parse(sql);
144+
System.out.println(statement.toString());
145+
Select select = (Select) statement;
146+
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
147+
assertTrue(plainSelect.getKsqlWindow().isTumblingWindow());
148+
assertFalse(plainSelect.getKsqlWindow().isSessionWindow());
149+
assertFalse(plainSelect.getKsqlWindow().isHoppingWindow());
150+
assertEquals(30L, plainSelect.getKsqlWindow().getSizeDuration());
151+
assertEquals("SECONDS", plainSelect.getKsqlWindow().getSizeTimeUnit().toString());
152+
153+
assertStatementCanBeDeparsedAs(select, sql, true);
154+
assertSqlCanBeParsedAndDeparsed(sql, true);
155+
}
83156
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3304,7 +3304,7 @@ public void testMultiPartNamesIssue163() throws JSQLParserException {
33043304

33053305
@Test
33063306
public void testMultiPartNamesIssue608() throws JSQLParserException {
3307-
assertSqlCanBeParsedAndDeparsed("SELECT @@session.tx_read_only");
3307+
assertSqlCanBeParsedAndDeparsed("SELECT @@sessions.tx_read_only");
33083308
}
33093309

33103310
// Teradata allows SEL to be used in place of SELECT

0 commit comments

Comments
 (0)