Skip to content

Commit cd8889c

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 4f1f00f commit cd8889c

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
@@ -8203,10 +8203,10 @@ PlainSelect PlainSelect() #PlainSelect:
82038203
windowName = RelObjectName() <K_AS> winDef = windowDefinition() { List<WindowDefinition> winDefs = new ArrayList<WindowDefinition>(); winDefs.add(winDef.withWindowName(windowName)); }
82048204
( LOOKAHEAD(2) "," windowName = RelObjectName() <K_AS> winDef = windowDefinition() { winDefs.add(winDef.withWindowName(windowName)); } )*
82058205
{ plainSelect.setWindowDefinitions(winDefs); }
8206+
// DuckDB: QUALIFY after WINDOW
8207+
[ LOOKAHEAD(2, { plainSelect.getQualify() == null }) qualify=Qualify()
8208+
{ plainSelect.setQualify(qualify); plainSelect.setQualifyAfterWindow(true); } ]
82068209
]
8207-
// DuckDB: QUALIFY after WINDOW
8208-
[ LOOKAHEAD(2, { plainSelect.getQualify() == null }) qualify=Qualify()
8209-
{ plainSelect.setQualify(qualify); plainSelect.setQualifyAfterWindow(true); } ]
82108210
[ LOOKAHEAD(<K_ORDER> <K_BY>) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ]
82118211
[ LOOKAHEAD(2) interpolateElements = InterpolateClause() { plainSelect.setInterpolate(interpolateElements); } ]
82128212
[ LOOKAHEAD(2) forClause = ForClause() {plainSelect.setForClause(forClause);} ]
@@ -13212,7 +13212,13 @@ void windowFun(AnalyticExpression retval):{
1321213212
(
1321313213
windowName = RelObjectName() { retval.setWindowName(windowName); }
1321413214
|
13215-
winDef = windowDefinition() { retval.setWindowDefinition(winDef); }
13215+
winDef = windowDefinition()
13216+
{
13217+
if (winDef.getBaseWindowName() != null && retval.getType() != AnalyticType.OVER) {
13218+
throw new ParseException("Only an OVER window may extend a named window");
13219+
}
13220+
retval.setWindowDefinition(winDef);
13221+
}
1321613222

1321713223
[
1321813224
LOOKAHEAD(2) <K_OVER> "("
@@ -13234,9 +13240,14 @@ WindowDefinition windowDefinition() : {
1323413240
String baseWindowName;
1323513241
} {
1323613242
"("
13237-
// DuckDB: (w ORDER BY b) extends the named window w
13238-
[ LOOKAHEAD({ isBaseWindowNameAhead() }) baseWindowName=RelObjectName()
13239-
{ winDef.setBaseWindowName(baseWindowName); } ]
13243+
// DuckDB: (w ORDER BY b) extends the named window w. A choice, not an optional, so that
13244+
// lookahead scans through a window specification keep the check
13245+
(
13246+
LOOKAHEAD({ isBaseWindowNameAhead() }) baseWindowName=RelObjectName()
13247+
{ winDef.setBaseWindowName(baseWindowName); }
13248+
|
13249+
{}
13250+
)
1324013251
[<K_PARTITION> <K_BY>
1324113252
(LOOKAHEAD(3) expressionList=ComplexExpressionList()
1324213253
| "(" {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
@@ -997,7 +997,10 @@ void testWindowAndFilterForms() throws JSQLParserException {
997997
"SELECT sum(a) OVER w2 FROM t WINDOW w AS (PARTITION BY c), w2 AS (w ORDER BY b)",
998998
"SELECT sum(a) OVER (ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
999999
"SELECT sum(a) FILTER (b > 1) FROM t",
1000-
"SELECT count(*) FILTER (b > 1) OVER () FROM t"}) {
1000+
"SELECT count(*) FILTER (b > 1) OVER () FROM t",
1001+
// window specifications inside syntactic lookahead
1002+
"SELECT JSON_VALUE(j, '$.a' DEFAULT sum(a) OVER (PARTITION BY b) ON EMPTY) FROM t",
1003+
"SELECT JSON_VALUE(j, '$.a' DEFAULT sum(a) OVER (w ORDER BY b) ON EMPTY) FROM t"}) {
10011004
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
10021005
}
10031006
PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(
@@ -1006,6 +1009,10 @@ void testWindowAndFilterForms() throws JSQLParserException {
10061009
(net.sf.jsqlparser.expression.AnalyticExpression) select.getSelectItem(0)
10071010
.getExpression();
10081011
Assertions.assertFalse(sum.isFilterWhereKeyword());
1012+
Assertions.assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(
1013+
"SELECT percentile_cont(0.5) WITHIN GROUP (w ORDER BY a) FROM t"));
1014+
Assertions.assertThrows(JSQLParserException.class,
1015+
() -> CCJSqlParserUtil.parse("SELECT a FROM t ORDER SIBLINGS BY a QUALIFY x = 1"));
10091016
// PostgreSQL requires WHERE
10101017
Assertions.assertThrows(JSQLParserException.class,
10111018
() -> CCJSqlParserUtil.parse("SELECT sum(a) FILTER (b > 1) FROM t",

0 commit comments

Comments
 (0)