Skip to content

Commit ae665e6

Browse files
lmontrieuxwumpz
authored andcommitted
Support KSQL's WITHIN (#722)
* Implements WITHIN for KSQL windowed joins * Clean up * Improve test * Implements WITHIN ( before TimeUnit, after TimeUnit ) for KSQL Also restricts TimeUnit to units accepted by KSQL * WITHIN should come before ON
1 parent 11be715 commit ae665e6

File tree

5 files changed

+256
-2
lines changed

5 files changed

+256
-2
lines changed

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class Join extends ASTNodeAccessImpl {
4444
private FromItem rightItem;
4545
private Expression onExpression;
4646
private List<Column> usingColumns;
47+
private KSQLJoinWindow joinWindow;
4748

4849
/**
4950
* Whether is a tab1,tab2 join
@@ -190,6 +191,21 @@ public void setUsingColumns(List<Column> list) {
190191
usingColumns = list;
191192
}
192193

194+
195+
public boolean isWindowJoin() {
196+
return joinWindow != null;
197+
}
198+
/**
199+
* Return the "WITHIN" join window (if any)
200+
*/
201+
public KSQLJoinWindow getJoinWindow() {
202+
return joinWindow;
203+
}
204+
205+
public void setJoinWindow(KSQLJoinWindow joinWindow) {
206+
this.joinWindow = joinWindow;
207+
}
208+
193209
@Override
194210
public String toString() {
195211
if (isSimple()) {
@@ -217,7 +233,8 @@ public String toString() {
217233
type += "SEMI ";
218234
}
219235

220-
return type + "JOIN " + rightItem + ((onExpression != null) ? " ON " + onExpression + "" : "")
236+
return type + "JOIN " + rightItem + ((joinWindow != null) ? " WITHIN " + joinWindow : "")
237+
+ ((onExpression != null) ? " ON " + onExpression + "" : "")
221238
+ PlainSelect.getFormatedList(usingColumns, "USING", true, true);
222239
}
223240

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2018 JSQLParser
6+
* %%
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Lesser General Public License as
9+
* published by the Free Software Foundation, either version 2.1 of the
10+
* License, or (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Lesser Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Lesser Public
18+
* License along with this program. If not, see
19+
* <http://www.gnu.org/licenses/lgpl-2.1.html>.
20+
* #L%
21+
*/
22+
package net.sf.jsqlparser.statement.select;
23+
24+
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
25+
26+
public class KSQLJoinWindow extends ASTNodeAccessImpl {
27+
28+
public enum TimeUnit {
29+
DAY ("DAY"),
30+
HOUR ("HOUR"),
31+
MINUTE ("MINUTE"),
32+
SECOND ("SECOND"),
33+
MILLISECOND ("MILLISECOND"),
34+
DAYS ("DAYS"),
35+
HOURS ("HOURS"),
36+
MINUTES ("MINUTES"),
37+
SECONDS ("SECONDS"),
38+
MILLISECONDS ("MILLISECONDS");
39+
40+
private String timeUnit;
41+
42+
TimeUnit(String timeUnit) {
43+
this.timeUnit = timeUnit;
44+
}
45+
46+
public String getTimeUnit() {
47+
return timeUnit;
48+
}
49+
}
50+
51+
private boolean beforeAfter;
52+
private long duration;
53+
private TimeUnit timeUnit;
54+
private long beforeDuration;
55+
private TimeUnit beforeTimeUnit;
56+
private long afterDuration;
57+
private TimeUnit afterTimeUnit;
58+
59+
public KSQLJoinWindow() {
60+
}
61+
62+
public boolean isBeforeAfterWindow() {
63+
return beforeAfter;
64+
}
65+
66+
public void setBeforeAfterWindow(boolean beforeAfter) {
67+
this.beforeAfter = beforeAfter;
68+
}
69+
70+
public long getDuration() {
71+
return duration;
72+
}
73+
74+
public void setDuration(long duration) {
75+
this.duration = duration;
76+
}
77+
78+
public TimeUnit getTimeUnit() {
79+
return timeUnit;
80+
}
81+
82+
public void setTimeUnit(TimeUnit timeUnit) {
83+
this.timeUnit = timeUnit;
84+
}
85+
86+
public long getBeforeDuration() {
87+
return beforeDuration;
88+
}
89+
90+
public void setBeforeDuration(long beforeDuration) {
91+
this.beforeDuration = beforeDuration;
92+
}
93+
94+
public TimeUnit getBeforeTimeUnit() {
95+
return beforeTimeUnit;
96+
}
97+
98+
public void setBeforeTimeUnit(TimeUnit beforeTimeUnit) {
99+
this.beforeTimeUnit = beforeTimeUnit;
100+
}
101+
102+
public long getAfterDuration() {
103+
return afterDuration;
104+
}
105+
106+
public void setAfterDuration(long afterDuration) {
107+
this.afterDuration = afterDuration;
108+
}
109+
110+
public TimeUnit getAfterTimeUnit() {
111+
return afterTimeUnit;
112+
}
113+
114+
public void setAfterTimeUnit(TimeUnit afterTimeUnit) {
115+
this.afterTimeUnit = afterTimeUnit;
116+
}
117+
118+
@Override
119+
public String toString() {
120+
if (isBeforeAfterWindow()) {
121+
return "(" + beforeDuration + " " + beforeTimeUnit + ", " + afterDuration + " " + afterTimeUnit + ")";
122+
}
123+
return "(" + duration + " " + timeUnit + ")";
124+
}
125+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import net.sf.jsqlparser.statement.select.*;
2828
import net.sf.jsqlparser.statement.values.ValuesStatement;
2929

30+
import java.util.Iterator;
31+
import java.util.List;
32+
3033
/**
3134
* A class to de-parse (that is, tranform from JSqlParser hierarchy into a string) a
3235
* {@link net.sf.jsqlparser.statement.select.Select}
@@ -375,6 +378,10 @@ public void deparseJoin(Join join) {
375378

376379
FromItem fromItem = join.getRightItem();
377380
fromItem.accept(this);
381+
if (join.isWindowJoin()) {
382+
buffer.append(" WITHIN ");
383+
buffer.append(join.getJoinWindow().toString());
384+
}
378385
if (join.getOnExpression() != null) {
379386
buffer.append(" ON ");
380387
join.getOnExpression().accept(expressionVisitor);

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

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,7 @@ Join JoinerExpression() #JoinerExpression:
17051705
Expression onExpression = null;
17061706
Column tableColumn;
17071707
List<Column> columns = null;
1708+
KSQLJoinWindow joinWindow = null;
17081709
}
17091710
{
17101711
[
@@ -1723,7 +1724,8 @@ Join JoinerExpression() #JoinerExpression:
17231724

17241725

17251726
[
1726-
LOOKAHEAD(2) (( <K_ON> onExpression=Expression() { join.setOnExpression(onExpression); } )
1727+
LOOKAHEAD(2) ([ <K_WITHIN> "(" joinWindow = JoinWindow() ")" {join.setJoinWindow(joinWindow);}]
1728+
( <K_ON> onExpression=Expression() { join.setOnExpression(onExpression); })
17271729
|
17281730
( <K_USING> "(" tableColumn=Column() { columns = new ArrayList(); columns.add(tableColumn); }
17291731
("," tableColumn=Column() { columns.add(tableColumn); } )* ")"
@@ -1734,6 +1736,35 @@ Join JoinerExpression() #JoinerExpression:
17341736
join.setRightItem(right);
17351737
return join;
17361738
}
1739+
1740+
}
1741+
1742+
KSQLJoinWindow JoinWindow():
1743+
{
1744+
KSQLJoinWindow retval = new KSQLJoinWindow();
1745+
boolean beforeAfter;
1746+
Token beforeDurationToken = null;
1747+
Token beforeTimeUnitToken = null;
1748+
Token afterDurationToken = null;
1749+
Token afterTimeUnitToken = null;
1750+
}
1751+
{
1752+
(beforeDurationToken=<S_LONG> beforeTimeUnitToken=<S_IDENTIFIER>
1753+
[ "," afterDurationToken=<S_LONG> afterTimeUnitToken=<S_IDENTIFIER> ]
1754+
{
1755+
if (afterDurationToken == null) {
1756+
retval.setDuration(Long.parseLong(beforeDurationToken.image));
1757+
retval.setTimeUnit(KSQLJoinWindow.TimeUnit.valueOf(beforeTimeUnitToken.image));
1758+
retval.setBeforeAfterWindow(false);
1759+
return retval;
1760+
}
1761+
retval.setBeforeDuration(Long.parseLong(beforeDurationToken.image));
1762+
retval.setBeforeTimeUnit(KSQLJoinWindow.TimeUnit.valueOf(beforeTimeUnitToken.image));
1763+
retval.setAfterDuration(Long.parseLong(afterDurationToken.image));
1764+
retval.setAfterTimeUnit(KSQLJoinWindow.TimeUnit.valueOf(afterTimeUnitToken.image));
1765+
retval.setBeforeAfterWindow(true);
1766+
return retval;
1767+
})
17371768
}
17381769

17391770
Expression WhereClause():
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package net.sf.jsqlparser.statement.select;
2+
3+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
4+
import net.sf.jsqlparser.schema.Table;
5+
import net.sf.jsqlparser.statement.Statement;
6+
import org.junit.Test;
7+
8+
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
9+
import static net.sf.jsqlparser.test.TestUtils.assertStatementCanBeDeparsedAs;
10+
import static org.junit.Assert.assertEquals;
11+
import static org.junit.Assert.assertFalse;
12+
import static org.junit.Assert.assertTrue;
13+
14+
public class KSQLTest {
15+
16+
@Test
17+
public void testKSQLWindowedJoin() throws Exception {
18+
String sql;
19+
Statement statement;
20+
21+
sql = "SELECT *\n"
22+
+ "FROM table1 t1\n"
23+
+ "INNER JOIN table2 t2\n"
24+
+ "WITHIN (5 HOURS)\n"
25+
+ "ON t1.id = t2.id\n";
26+
27+
statement = CCJSqlParserUtil.parse(sql);
28+
29+
System.out.println(statement.toString());
30+
31+
Select select = (Select) statement;
32+
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
33+
assertEquals(1, plainSelect.getJoins().size());
34+
assertEquals("table2", ((Table) plainSelect.getJoins().get(0).getRightItem()).
35+
getFullyQualifiedName());
36+
assertTrue(plainSelect.getJoins().get(0).isWindowJoin());
37+
assertEquals(5L, plainSelect.getJoins().get(0).getJoinWindow().getDuration());
38+
assertEquals("HOURS", plainSelect.getJoins().get(0).getJoinWindow().getTimeUnit().toString());
39+
assertFalse(plainSelect.getJoins().get(0).getJoinWindow().isBeforeAfterWindow());
40+
assertStatementCanBeDeparsedAs(select, sql, true);
41+
42+
assertSqlCanBeParsedAndDeparsed(sql, true);
43+
}
44+
45+
@Test
46+
public void testKSQLBeforeAfterWindowedJoin() throws Exception {
47+
String sql;
48+
Statement statement;
49+
sql = "SELECT *\n"
50+
+ "FROM table1 t1\n"
51+
+ "INNER JOIN table2 t2\n"
52+
+ "WITHIN (2 MINUTES, 5 MINUTES)\n"
53+
+ "ON t1.id = t2.id\n";
54+
55+
statement = CCJSqlParserUtil.parse(sql);
56+
57+
System.out.println(statement.toString());
58+
59+
Select select = (Select) statement;
60+
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
61+
assertEquals(1, plainSelect.getJoins().size());
62+
assertEquals("table2", ((Table) plainSelect.getJoins().get(0).getRightItem()).
63+
getFullyQualifiedName());
64+
assertTrue(plainSelect.getJoins().get(0).isWindowJoin());
65+
assertEquals(2L, plainSelect.getJoins().get(0).getJoinWindow().getBeforeDuration());
66+
assertEquals("MINUTES", plainSelect.getJoins().get(0).getJoinWindow().getBeforeTimeUnit().toString());
67+
assertEquals(5L, plainSelect.getJoins().get(0).getJoinWindow().getAfterDuration());
68+
assertEquals("MINUTES", plainSelect.getJoins().get(0).getJoinWindow().getAfterTimeUnit().toString());
69+
assertTrue(plainSelect.getJoins().get(0).getJoinWindow().isBeforeAfterWindow());
70+
assertStatementCanBeDeparsedAs(select, sql, true);
71+
72+
assertSqlCanBeParsedAndDeparsed(sql, true);
73+
}
74+
}

0 commit comments

Comments
 (0)