From 324891a6810601f39d42ca3d74d3d711c867d231 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 29 Oct 2026 10:23:24 +0100 Subject: [PATCH] fix: parse a bracketed operand at the start of a PARTITION BY list `SELECT SUM(x) OVER (PARTITION BY (1 + a) / 2) FROM t` failed with a ParseException at the `/`. The four PARTITION BY sites chose between `LOOKAHEAD(3) ComplexExpressionList()` and a bracketed list `"(" ComplexExpressionList() ")"`. The probe scans into `Expression()`, whose binary operators live in a Java loop the scan cannot see, so it failed on `( 1 +`, the bracketed alternative won, parsed `(1 + a)` as the whole list and then met `/`. The bracketed alternative only won when the probe failed, and the parse then failed too, so dropping it changes nothing that parsed before. All four sites now call `ComplexExpressionList()` directly. --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 27 ++++++------------- .../expression/AnalyticExpressionTest.java | 19 +++++++++++++ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 7339e68ba3..c3c20a0188 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -9475,7 +9475,6 @@ PlainSelect PlainSelect() #PlainSelect: OracleHierarchicalExpression oracleHierarchicalQueryClause = null; PreferringClause preferringClause = null; ExpressionList expressionList = null; - boolean partitionByBrackets = false; List intoTables = null; MySqlSelectIntoClause mySqlSelectIntoClause = null; MySqlProcedureAnalyse mySqlProcedureAnalyse = null; @@ -9581,13 +9580,9 @@ PlainSelect PlainSelect() #PlainSelect: [ LOOKAHEAD(2) oracleHierarchicalQueryClause=OracleHierarchicalQueryClause() { plainSelect.setOracleHierarchical(oracleHierarchicalQueryClause); } ] [ LOOKAHEAD(2) preferringClause=PreferringClause() { plainSelect.setPreferringClause(preferringClause); } [LOOKAHEAD(2) - ( - LOOKAHEAD(2) expressionList=ComplexExpressionList() - | - "(" {partitionByBrackets = true;} expressionList=ComplexExpressionList() ")" - ) + expressionList=ComplexExpressionList() { - preferringClause.setPartitionExpressionList(expressionList, partitionByBrackets); + preferringClause.setPartitionExpressionList(expressionList, false); } ] ] @@ -14463,7 +14458,6 @@ JsonAggregateFunction JsonAggregateFunction() : { ExpressionList expressionList = null; List olist = null; WindowElement windowElement = null; - boolean partitionByBrackets = false; } { ( @@ -14550,13 +14544,12 @@ JsonAggregateFunction JsonAggregateFunction() : { {result.setAnalyticType(AnalyticType.OVER);} "(" [ - (LOOKAHEAD(3) expressionList=ComplexExpressionList() - | "(" {partitionByBrackets = true;} expressionList=ComplexExpressionList() ")" ) + expressionList=ComplexExpressionList() ] [olist=OrderByElements() ] [windowElement = WindowElement() ] { - result.setPartitionExpressionList(expressionList, partitionByBrackets); + result.setPartitionExpressionList(expressionList, false); result.setOrderByElements(olist); result.setWindowElement(windowElement); } @@ -14698,7 +14691,6 @@ KeepExpression KeepExpression() : { void windowFun(AnalyticExpression retval):{ ExpressionList expressionList = null; - boolean partitionByBrackets = false; String windowName = null; WindowDefinition winDef; } { @@ -14722,10 +14714,9 @@ void windowFun(AnalyticExpression retval):{ [ LOOKAHEAD(2) "(" [ - (LOOKAHEAD(3) expressionList=ComplexExpressionList() - | "(" {partitionByBrackets = true;} expressionList=ComplexExpressionList() ")" ) + expressionList=ComplexExpressionList() ] - ")" { winDef.setPartitionExpressionList(expressionList, partitionByBrackets); retval.setType(AnalyticType.WITHIN_GROUP_OVER); } + ")" { winDef.setPartitionExpressionList(expressionList, false); retval.setType(AnalyticType.WITHIN_GROUP_OVER); } ] ) } @@ -14734,7 +14725,6 @@ WindowDefinition windowDefinition() : { ExpressionList expressionList = null; List olist = null; WindowElement windowElement = null; - boolean partitionByBrackets = false; WindowDefinition winDef = new WindowDefinition(); String baseWindowName; } { @@ -14748,13 +14738,12 @@ WindowDefinition windowDefinition() : { {} ) [ - (LOOKAHEAD(3) expressionList=ComplexExpressionList() - | "(" {partitionByBrackets = true;} expressionList=ComplexExpressionList() ")" ) + expressionList=ComplexExpressionList() ] [olist=OrderByElements() ] [windowElement = WindowElement() ] { - winDef.setPartitionExpressionList(expressionList, partitionByBrackets); + winDef.setPartitionExpressionList(expressionList, false); winDef.setOrderByElements(olist); winDef.setWindowElement(windowElement); } diff --git a/src/test/java/net/sf/jsqlparser/expression/AnalyticExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/AnalyticExpressionTest.java index df4ef1eba5..b1a7b059f9 100644 --- a/src/test/java/net/sf/jsqlparser/expression/AnalyticExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/AnalyticExpressionTest.java @@ -43,4 +43,23 @@ void testDatabricks() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + @Test + void testPartitionByBracketedOperand() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT SUM(x) OVER (PARTITION BY (1 + a) / 2 ORDER BY b) FROM t", true); + + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT SUM(x) OVER w FROM t WINDOW w AS (PARTITION BY (1 + a) / 2)", true); + + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT LISTAGG(x, ',') WITHIN GROUP (ORDER BY y) OVER (PARTITION BY (1 + a) / 2) FROM t", + true); + + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t PREFERRING HIGH x PARTITION BY (1 + a) / 2", true); + + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT SUM(x) OVER (PARTITION BY (a, b), c) FROM t", true); + } }