Skip to content

Commit e1d2f66

Browse files
committed
Keep window specifications parseable inside lookahead
- The base window name is a choice rather than an optional, so that lookahead scans through a window specification keep its check: `JSON_VALUE(... DEFAULT sum(a) OVER (PARTITION BY b) ON EMPTY)` parses again. - Only an OVER window may extend a named window, not WITHIN GROUP. - QUALIFY after WINDOW requires a WINDOW clause.
1 parent 131056c commit e1d2f66

2 files changed

Lines changed: 26 additions & 8 deletions

File tree

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8104,10 +8104,10 @@ PlainSelect PlainSelect() #PlainSelect:
81048104
windowName = RelObjectName() <K_AS> winDef = windowDefinition() { List<WindowDefinition> winDefs = new ArrayList<WindowDefinition>(); winDefs.add(winDef.withWindowName(windowName)); }
81058105
( LOOKAHEAD(2) "," windowName = RelObjectName() <K_AS> winDef = windowDefinition() { winDefs.add(winDef.withWindowName(windowName)); } )*
81068106
{ plainSelect.setWindowDefinitions(winDefs); }
8107+
// DuckDB: QUALIFY after WINDOW
8108+
[ LOOKAHEAD(2, { plainSelect.getQualify() == null }) qualify=Qualify()
8109+
{ plainSelect.setQualify(qualify); plainSelect.setQualifyAfterWindow(true); } ]
81078110
]
8108-
// DuckDB: QUALIFY after WINDOW
8109-
[ LOOKAHEAD(2, { plainSelect.getQualify() == null }) qualify=Qualify()
8110-
{ plainSelect.setQualify(qualify); plainSelect.setQualifyAfterWindow(true); } ]
81118111
[ LOOKAHEAD(<K_ORDER> <K_BY>) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ]
81128112
[ LOOKAHEAD(2) interpolateElements = InterpolateClause() { plainSelect.setInterpolate(interpolateElements); } ]
81138113
[ LOOKAHEAD(2) forClause = ForClause() {plainSelect.setForClause(forClause);} ]
@@ -13030,7 +13030,13 @@ void windowFun(AnalyticExpression retval):{
1303013030
(
1303113031
windowName = RelObjectName() { retval.setWindowName(windowName); }
1303213032
|
13033-
winDef = windowDefinition() { retval.setWindowDefinition(winDef); }
13033+
winDef = windowDefinition()
13034+
{
13035+
if (winDef.getBaseWindowName() != null && retval.getType() != AnalyticType.OVER) {
13036+
throw new ParseException("Only an OVER window may extend a named window");
13037+
}
13038+
retval.setWindowDefinition(winDef);
13039+
}
1303413040

1303513041
[
1303613042
LOOKAHEAD(2) <K_OVER> "("
@@ -13052,9 +13058,14 @@ WindowDefinition windowDefinition() : {
1305213058
String baseWindowName;
1305313059
} {
1305413060
"("
13055-
// DuckDB: (w ORDER BY b) extends the named window w
13056-
[ LOOKAHEAD({ isBaseWindowNameAhead() }) baseWindowName=RelObjectName()
13057-
{ winDef.setBaseWindowName(baseWindowName); } ]
13061+
// DuckDB: (w ORDER BY b) extends the named window w. A choice, not an optional, so that
13062+
// lookahead scans through a window specification keep the check
13063+
(
13064+
LOOKAHEAD({ isBaseWindowNameAhead() }) baseWindowName=RelObjectName()
13065+
{ winDef.setBaseWindowName(baseWindowName); }
13066+
|
13067+
{}
13068+
)
1305813069
[<K_PARTITION> <K_BY>
1305913070
(LOOKAHEAD(3) expressionList=ComplexExpressionList()
1306013071
| "(" {partitionByBrackets = true;} expressionList=ComplexExpressionList() ")" )

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,10 @@ void testWindowAndFilterForms() throws JSQLParserException {
876876
"SELECT sum(a) OVER w2 FROM t WINDOW w AS (PARTITION BY c), w2 AS (w ORDER BY b)",
877877
"SELECT sum(a) OVER (ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
878878
"SELECT sum(a) FILTER (b > 1) FROM t",
879-
"SELECT count(*) FILTER (b > 1) OVER () FROM t"}) {
879+
"SELECT count(*) FILTER (b > 1) OVER () FROM t",
880+
// window specifications inside syntactic lookahead
881+
"SELECT JSON_VALUE(j, '$.a' DEFAULT sum(a) OVER (PARTITION BY b) ON EMPTY) FROM t",
882+
"SELECT JSON_VALUE(j, '$.a' DEFAULT sum(a) OVER (w ORDER BY b) ON EMPTY) FROM t"}) {
880883
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
881884
}
882885
PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(
@@ -885,6 +888,10 @@ void testWindowAndFilterForms() throws JSQLParserException {
885888
(net.sf.jsqlparser.expression.AnalyticExpression) select.getSelectItem(0)
886889
.getExpression();
887890
Assertions.assertFalse(sum.isFilterWhereKeyword());
891+
Assertions.assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(
892+
"SELECT percentile_cont(0.5) WITHIN GROUP (w ORDER BY a) FROM t"));
893+
Assertions.assertThrows(JSQLParserException.class,
894+
() -> CCJSqlParserUtil.parse("SELECT a FROM t ORDER SIBLINGS BY a QUALIFY x = 1"));
888895
// PostgreSQL requires WHERE
889896
Assertions.assertThrows(JSQLParserException.class,
890897
() -> CCJSqlParserUtil.parse("SELECT sum(a) FILTER (b > 1) FROM t",

0 commit comments

Comments
 (0)