From 321c88098a757911dd406cb97ea0555415ad7056 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Sun, 21 Apr 2024 01:57:02 +0700 Subject: [PATCH] feat: RedShift specific Window function IGNORE | RESPECT NULLS Signed-off-by: Andreas Reichel --- .../expression/AnalyticExpression.java | 14 +++++++++++--- .../util/deparser/ExpressionDeParser.java | 13 ++++++++++--- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 15 ++++++++++++++- .../statement/select/WindowFunctionTest.java | 19 +++++++++++++++++-- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java b/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java index 4fd4d27ad..cae3d07fe 100644 --- a/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java @@ -277,7 +277,7 @@ public String toString() { havingClause.appendTo(b); } - if (nullHandling != null) { + if (nullHandling != null && !ignoreNullsOutside) { switch (nullHandling) { case IGNORE_NULLS: b.append(" IGNORE NULLS"); @@ -287,6 +287,7 @@ public String toString() { break; } } + if (funcOrderBy != null) { b.append(" ORDER BY "); b.append(funcOrderBy.stream().map(OrderByElement::toString).collect(joining(", "))); @@ -310,8 +311,15 @@ public String toString() { } } - if (isIgnoreNullsOutside()) { - b.append("IGNORE NULLS "); + if (nullHandling != null && ignoreNullsOutside) { + switch (nullHandling) { + case IGNORE_NULLS: + b.append(" IGNORE NULLS "); + break; + case RESPECT_NULLS: + b.append(" RESPECT NULLS "); + break; + } } switch (type) { diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java index 8441a1a6d..785e3f25f 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -823,7 +823,7 @@ public void visit(AnalyticExpression aexpr) { havingClause.getExpression().accept(this); } - if (aexpr.getNullHandling() != null) { + if (aexpr.getNullHandling() != null && !aexpr.isIgnoreNullsOutside()) { switch (aexpr.getNullHandling()) { case IGNORE_NULLS: buffer.append(" IGNORE NULLS"); @@ -858,8 +858,15 @@ public void visit(AnalyticExpression aexpr) { } } - if (aexpr.isIgnoreNullsOutside()) { - buffer.append("IGNORE NULLS "); + if (aexpr.getNullHandling() != null && aexpr.isIgnoreNullsOutside()) { + switch (aexpr.getNullHandling()) { + case IGNORE_NULLS: + buffer.append(" IGNORE NULLS "); + break; + case RESPECT_NULLS: + buffer.append(" RESPECT NULLS "); + break; + } } switch (aexpr.getType()) { diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 6169f11c9..a07b0aad4 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -69,10 +69,14 @@ import net.sf.jsqlparser.statement.grant.*; import java.util.*; import java.util.AbstractMap.SimpleEntry; +import java.util.logging.Level; +import java.util.logging.Logger; + /** * The parser generated by JavaCC */ public class CCJSqlParser extends AbstractJSqlParser { + public final static Logger LOGGER = Logger.getLogger(CCJSqlParser.class.getName()); public int bracketsCounter = 0; public int caseCounter = 0; public boolean interrupted = false; @@ -5050,7 +5054,16 @@ void windowFun(AnalyticExpression retval):{ WindowDefinition winDef; } { ( - [ { retval.setIgnoreNullsOutside(true); } ] + [ + ( + { retval.setNullHandling(Function.NullHandling.IGNORE_NULLS); retval.setIgnoreNullsOutside(true); } + ) + | + ( + { retval.setNullHandling(Function.NullHandling.RESPECT_NULLS); retval.setIgnoreNullsOutside(true); } + ) + ] + {retval.setType(AnalyticType.OVER);} | {retval.setType(AnalyticType.WITHIN_GROUP);} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/WindowFunctionTest.java b/src/test/java/net/sf/jsqlparser/statement/select/WindowFunctionTest.java index e6d8f1e92..6a0beb7ac 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/WindowFunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/WindowFunctionTest.java @@ -18,8 +18,23 @@ public class WindowFunctionTest { public void testListAggOverIssue1652() throws JSQLParserException { String sqlString = "SELECT\n" + - " LISTAGG (d.COL_TO_AGG, ' / ') WITHIN GROUP (ORDER BY d.COL_TO_AGG) OVER (PARTITION BY d.PART_COL) AS MY_LISTAGG\n" + - "FROM cte_dummy_data d"; + " LISTAGG (d.COL_TO_AGG, ' / ') WITHIN GROUP (ORDER BY d.COL_TO_AGG) OVER (PARTITION BY d.PART_COL) AS MY_LISTAGG\n" + + + "FROM cte_dummy_data d"; + + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true); + } + + @Test + public void RedshiftRespectIgnoreNulls() throws JSQLParserException { + String sqlString = + "select venuestate, venueseats, venuename,\n" + + "first_value(venuename) ignore nulls\n" + + "over(partition by venuestate\n" + + "order by venueseats desc\n" + + "rows between unbounded preceding and unbounded following) AS first\n" + + "from (select * from venue where venuestate='CA')\n" + + "order by venuestate;"; TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true); }