Skip to content

Commit 6a340be

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 46d6143 commit 6a340be

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
@@ -8299,10 +8299,10 @@ PlainSelect PlainSelect() #PlainSelect:
82998299
windowName = RelObjectName() <K_AS> winDef = windowDefinition() { List<WindowDefinition> winDefs = new ArrayList<WindowDefinition>(); winDefs.add(winDef.withWindowName(windowName)); }
83008300
( LOOKAHEAD(2) "," windowName = RelObjectName() <K_AS> winDef = windowDefinition() { winDefs.add(winDef.withWindowName(windowName)); } )*
83018301
{ plainSelect.setWindowDefinitions(winDefs); }
8302+
// DuckDB: QUALIFY after WINDOW
8303+
[ LOOKAHEAD(2, { plainSelect.getQualify() == null }) qualify=Qualify()
8304+
{ plainSelect.setQualify(qualify); plainSelect.setQualifyAfterWindow(true); } ]
83028305
]
8303-
// DuckDB: QUALIFY after WINDOW
8304-
[ LOOKAHEAD(2, { plainSelect.getQualify() == null }) qualify=Qualify()
8305-
{ plainSelect.setQualify(qualify); plainSelect.setQualifyAfterWindow(true); } ]
83068306
[ LOOKAHEAD(<K_ORDER> <K_BY>) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ]
83078307
[ LOOKAHEAD(2) interpolateElements = InterpolateClause() { plainSelect.setInterpolate(interpolateElements); } ]
83088308
[ LOOKAHEAD(2) forClause = ForClause() {plainSelect.setForClause(forClause);} ]
@@ -13328,7 +13328,13 @@ void windowFun(AnalyticExpression retval):{
1332813328
(
1332913329
windowName = RelObjectName() { retval.setWindowName(windowName); }
1333013330
|
13331-
winDef = windowDefinition() { retval.setWindowDefinition(winDef); }
13331+
winDef = windowDefinition()
13332+
{
13333+
if (winDef.getBaseWindowName() != null && retval.getType() != AnalyticType.OVER) {
13334+
throw new ParseException("Only an OVER window may extend a named window");
13335+
}
13336+
retval.setWindowDefinition(winDef);
13337+
}
1333213338

1333313339
[
1333413340
LOOKAHEAD(2) <K_OVER> "("
@@ -13350,9 +13356,14 @@ WindowDefinition windowDefinition() : {
1335013356
String baseWindowName;
1335113357
} {
1335213358
"("
13353-
// DuckDB: (w ORDER BY b) extends the named window w
13354-
[ LOOKAHEAD({ isBaseWindowNameAhead() }) baseWindowName=RelObjectName()
13355-
{ winDef.setBaseWindowName(baseWindowName); } ]
13359+
// DuckDB: (w ORDER BY b) extends the named window w. A choice, not an optional, so that
13360+
// lookahead scans through a window specification keep the check
13361+
(
13362+
LOOKAHEAD({ isBaseWindowNameAhead() }) baseWindowName=RelObjectName()
13363+
{ winDef.setBaseWindowName(baseWindowName); }
13364+
|
13365+
{}
13366+
)
1335613367
[<K_PARTITION> <K_BY>
1335713368
(LOOKAHEAD(3) expressionList=ComplexExpressionList()
1335813369
| "(" {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
@@ -1032,7 +1032,10 @@ void testWindowAndFilterForms() throws JSQLParserException {
10321032
"SELECT sum(a) OVER w2 FROM t WINDOW w AS (PARTITION BY c), w2 AS (w ORDER BY b)",
10331033
"SELECT sum(a) OVER (ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
10341034
"SELECT sum(a) FILTER (b > 1) FROM t",
1035-
"SELECT count(*) FILTER (b > 1) OVER () FROM t"}) {
1035+
"SELECT count(*) FILTER (b > 1) OVER () FROM t",
1036+
// window specifications inside syntactic lookahead
1037+
"SELECT JSON_VALUE(j, '$.a' DEFAULT sum(a) OVER (PARTITION BY b) ON EMPTY) FROM t",
1038+
"SELECT JSON_VALUE(j, '$.a' DEFAULT sum(a) OVER (w ORDER BY b) ON EMPTY) FROM t"}) {
10361039
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
10371040
}
10381041
PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(
@@ -1041,6 +1044,10 @@ void testWindowAndFilterForms() throws JSQLParserException {
10411044
(net.sf.jsqlparser.expression.AnalyticExpression) select.getSelectItem(0)
10421045
.getExpression();
10431046
Assertions.assertFalse(sum.isFilterWhereKeyword());
1047+
Assertions.assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(
1048+
"SELECT percentile_cont(0.5) WITHIN GROUP (w ORDER BY a) FROM t"));
1049+
Assertions.assertThrows(JSQLParserException.class,
1050+
() -> CCJSqlParserUtil.parse("SELECT a FROM t ORDER SIBLINGS BY a QUALIFY x = 1"));
10441051
// PostgreSQL requires WHERE
10451052
Assertions.assertThrows(JSQLParserException.class,
10461053
() -> CCJSqlParserUtil.parse("SELECT sum(a) FILTER (b > 1) FROM t",

0 commit comments

Comments
 (0)