Skip to content

Commit 2f3123b

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 a21ef11 commit 2f3123b

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
@@ -8156,10 +8156,10 @@ PlainSelect PlainSelect() #PlainSelect:
81568156
windowName = RelObjectName() <K_AS> winDef = windowDefinition() { List<WindowDefinition> winDefs = new ArrayList<WindowDefinition>(); winDefs.add(winDef.withWindowName(windowName)); }
81578157
( LOOKAHEAD(2) "," windowName = RelObjectName() <K_AS> winDef = windowDefinition() { winDefs.add(winDef.withWindowName(windowName)); } )*
81588158
{ plainSelect.setWindowDefinitions(winDefs); }
8159+
// DuckDB: QUALIFY after WINDOW
8160+
[ LOOKAHEAD(2, { plainSelect.getQualify() == null }) qualify=Qualify()
8161+
{ plainSelect.setQualify(qualify); plainSelect.setQualifyAfterWindow(true); } ]
81598162
]
8160-
// DuckDB: QUALIFY after WINDOW
8161-
[ LOOKAHEAD(2, { plainSelect.getQualify() == null }) qualify=Qualify()
8162-
{ plainSelect.setQualify(qualify); plainSelect.setQualifyAfterWindow(true); } ]
81638163
[ LOOKAHEAD(<K_ORDER> <K_BY>) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ]
81648164
[ LOOKAHEAD(2) interpolateElements = InterpolateClause() { plainSelect.setInterpolate(interpolateElements); } ]
81658165
[ LOOKAHEAD(2) forClause = ForClause() {plainSelect.setForClause(forClause);} ]
@@ -13093,7 +13093,13 @@ void windowFun(AnalyticExpression retval):{
1309313093
(
1309413094
windowName = RelObjectName() { retval.setWindowName(windowName); }
1309513095
|
13096-
winDef = windowDefinition() { retval.setWindowDefinition(winDef); }
13096+
winDef = windowDefinition()
13097+
{
13098+
if (winDef.getBaseWindowName() != null && retval.getType() != AnalyticType.OVER) {
13099+
throw new ParseException("Only an OVER window may extend a named window");
13100+
}
13101+
retval.setWindowDefinition(winDef);
13102+
}
1309713103

1309813104
[
1309913105
LOOKAHEAD(2) <K_OVER> "("
@@ -13115,9 +13121,14 @@ WindowDefinition windowDefinition() : {
1311513121
String baseWindowName;
1311613122
} {
1311713123
"("
13118-
// DuckDB: (w ORDER BY b) extends the named window w
13119-
[ LOOKAHEAD({ isBaseWindowNameAhead() }) baseWindowName=RelObjectName()
13120-
{ winDef.setBaseWindowName(baseWindowName); } ]
13124+
// DuckDB: (w ORDER BY b) extends the named window w. A choice, not an optional, so that
13125+
// lookahead scans through a window specification keep the check
13126+
(
13127+
LOOKAHEAD({ isBaseWindowNameAhead() }) baseWindowName=RelObjectName()
13128+
{ winDef.setBaseWindowName(baseWindowName); }
13129+
|
13130+
{}
13131+
)
1312113132
[<K_PARTITION> <K_BY>
1312213133
(LOOKAHEAD(3) expressionList=ComplexExpressionList()
1312313134
| "(" {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
@@ -914,7 +914,10 @@ void testWindowAndFilterForms() throws JSQLParserException {
914914
"SELECT sum(a) OVER w2 FROM t WINDOW w AS (PARTITION BY c), w2 AS (w ORDER BY b)",
915915
"SELECT sum(a) OVER (ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
916916
"SELECT sum(a) FILTER (b > 1) FROM t",
917-
"SELECT count(*) FILTER (b > 1) OVER () FROM t"}) {
917+
"SELECT count(*) FILTER (b > 1) OVER () FROM t",
918+
// window specifications inside syntactic lookahead
919+
"SELECT JSON_VALUE(j, '$.a' DEFAULT sum(a) OVER (PARTITION BY b) ON EMPTY) FROM t",
920+
"SELECT JSON_VALUE(j, '$.a' DEFAULT sum(a) OVER (w ORDER BY b) ON EMPTY) FROM t"}) {
918921
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
919922
}
920923
PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(
@@ -923,6 +926,10 @@ void testWindowAndFilterForms() throws JSQLParserException {
923926
(net.sf.jsqlparser.expression.AnalyticExpression) select.getSelectItem(0)
924927
.getExpression();
925928
Assertions.assertFalse(sum.isFilterWhereKeyword());
929+
Assertions.assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(
930+
"SELECT percentile_cont(0.5) WITHIN GROUP (w ORDER BY a) FROM t"));
931+
Assertions.assertThrows(JSQLParserException.class,
932+
() -> CCJSqlParserUtil.parse("SELECT a FROM t ORDER SIBLINGS BY a QUALIFY x = 1"));
926933
// PostgreSQL requires WHERE
927934
Assertions.assertThrows(JSQLParserException.class,
928935
() -> CCJSqlParserUtil.parse("SELECT sum(a) FILTER (b > 1) FROM t",

0 commit comments

Comments
 (0)