Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion src/main/java/net/sf/jsqlparser/expression/WindowElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class WindowElement implements Serializable {
private Type type;
private WindowOffset offset;
private WindowRange range;
private Exclusion exclusion;

public Type getType() {
return type;
Expand All @@ -42,6 +43,14 @@ public void setRange(WindowRange range) {
this.range = range;
}

public Exclusion getExclusion() {
return exclusion;
}

public void setExclusion(Exclusion exclusion) {
this.exclusion = exclusion;
}

@Override
public String toString() {
StringBuilder buffer = new StringBuilder(type.toString());
Expand All @@ -52,6 +61,10 @@ public String toString() {
buffer.append(range.toString());
}

if (exclusion != null) {
buffer.append(" EXCLUDE ").append(exclusion);
}

return buffer.toString();
}

Expand All @@ -70,12 +83,32 @@ public WindowElement withRange(WindowRange range) {
return this;
}

public WindowElement withExclusion(Exclusion exclusion) {
this.setExclusion(exclusion);
return this;
}

public enum Type {
ROWS, RANGE;
ROWS, RANGE, GROUPS;

public static Type from(String type) {
return Enum.valueOf(Type.class, type.toUpperCase(Locale.ROOT));
}
}

public enum Exclusion {
CURRENT_ROW("CURRENT ROW"), GROUP("GROUP"), TIES("TIES"), NO_OTHERS("NO OTHERS");

private final String keyword;

Exclusion(String keyword) {
this.keyword = keyword;
}

@Override
public String toString() {
return keyword;
}
}

}
38 changes: 37 additions & 1 deletion src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,7 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */
| <K_GLOBAL:"GLOBAL">
| <K_GROUP:"GROUP">
| <K_GROUPING:"GROUPING">
| <K_GROUPS:"GROUPS">
| <K_HAVING:"HAVING">
| <K_IF:"IF">
| <K_IIF:"IIF">
Expand Down Expand Up @@ -9203,9 +9204,12 @@ WindowElement WindowElement():
WindowElement windowElement = new WindowElement();
WindowRange range = new WindowRange();
WindowOffset offset = null;
WindowElement.Exclusion exclusion = null;
}
{
(<K_ROWS> { windowElement.setType(WindowElement.Type.ROWS); } | <K_RANGE> { windowElement.setType(WindowElement.Type.RANGE); } )
(<K_ROWS> { windowElement.setType(WindowElement.Type.ROWS); }
| <K_RANGE> { windowElement.setType(WindowElement.Type.RANGE); }
| <K_GROUPS> { windowElement.setType(WindowElement.Type.GROUPS); })
( (
<K_BETWEEN> { windowElement.setRange(range); }
offset = WindowOffset() { range.setStart(offset); }
Expand All @@ -9214,12 +9218,44 @@ WindowElement WindowElement():
|
offset = WindowOffset() { windowElement.setOffset(offset); }
)
[ exclusion = FrameExclusion() { windowElement.setExclusion(exclusion); } ]

{
return windowElement;
}
}

WindowElement.Exclusion FrameExclusion():
{
WindowElement.Exclusion exclusion = null;
}
{
<K_EXCLUDE>
(
<K_CURRENT> <K_ROW>
{ exclusion = WindowElement.Exclusion.CURRENT_ROW; }
|
<K_GROUP>
{ exclusion = WindowElement.Exclusion.GROUP; }
|
LOOKAHEAD({
getToken(1).kind == S_IDENTIFIER
&& getToken(1).image.equalsIgnoreCase("TIES")
})
<S_IDENTIFIER>
{ exclusion = WindowElement.Exclusion.TIES; }
|
<K_NO>
LOOKAHEAD({
getToken(1).kind == S_IDENTIFIER
&& getToken(1).image.equalsIgnoreCase("OTHERS")
})
<S_IDENTIFIER>
{ exclusion = WindowElement.Exclusion.NO_OTHERS; }
)
{ return exclusion; }
}

WindowOffset WindowOffset():
{
WindowOffset offset = new WindowOffset();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@
*/
package net.sf.jsqlparser.statement.select;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.AnalyticExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.WindowElement;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.test.TestUtils;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -38,4 +46,71 @@ public void RedshiftRespectIgnoreNulls() throws JSQLParserException {

TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true);
}

@Test
public void testWindowFrameGroupsIssue2431() throws JSQLParserException {
String sqlString =
"SELECT SUM(value) OVER (ORDER BY ts "
+ "GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM events";

WindowElement windowElement = parseWindowElement(sqlString, 0);
assertEquals(WindowElement.Type.GROUPS, windowElement.getType());
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true);
}

@Test
public void testWindowFrameGroupsExcludeTiesIssue2431() throws JSQLParserException {
String sqlString =
"SELECT id, ts, value, SUM(value) OVER (ORDER BY ts "
+ "GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE TIES) AS sum_excl_ties "
+ "FROM events ORDER BY ts, id";

WindowElement windowElement = parseWindowElement(sqlString, 3);
assertEquals(WindowElement.Exclusion.TIES, windowElement.getExclusion());
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true);
}

@Test
public void testWindowFrameExclusionsIssue2431() throws JSQLParserException {
String[] sqlStrings = {
"SELECT SUM(value) OVER (ORDER BY ts ROWS UNBOUNDED PRECEDING EXCLUDE CURRENT ROW) FROM events",
"SELECT SUM(value) OVER (ORDER BY ts RANGE CURRENT ROW EXCLUDE GROUP) FROM events",
"SELECT SUM(value) OVER (ORDER BY ts GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE TIES) FROM events",
"SELECT SUM(value) OVER (ORDER BY ts GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) FROM events"
};

for (String sqlString : sqlStrings) {
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true);
}
}

@Test
public void testFrameExclusionIdentifierCompatibilityIssue2431() throws JSQLParserException {
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT ties FROM ties", true);
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT others FROM others", true);
}

@Test
public void testWindowFrameGroupsVariantsIssue2431() throws JSQLParserException {
String singleSidedSqlString =
"SELECT SUM(value) OVER (ORDER BY ts GROUPS UNBOUNDED PRECEDING) FROM events";
String namedWindowSqlString =
"SELECT SUM(value) OVER w FROM events "
+ "WINDOW w AS (ORDER BY ts GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW)";

TestUtils.assertSqlCanBeParsedAndDeparsed(singleSidedSqlString, true);
TestUtils.assertSqlCanBeParsedAndDeparsed(namedWindowSqlString, true);
}

private WindowElement parseWindowElement(String sqlString, int selectItemIndex)
throws JSQLParserException {
PlainSelect plainSelect = (PlainSelect) CCJSqlParserUtil.parse(sqlString);
Expression expression = plainSelect.getSelectItem(selectItemIndex).getExpression();
assertInstanceOf(AnalyticExpression.class, expression);
AnalyticExpression analyticExpression = (AnalyticExpression) expression;
WindowElement windowElement = analyticExpression.getWindowElement();

assertNotNull(windowElement);
return windowElement;
}
}
Loading