From 732e840e99740ff1949638a307f2d89900a018b3 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Mon, 25 Jul 2022 08:43:39 +0200 Subject: [PATCH] fixes #1581 --- README.md | 2 + .../expression/AnalyticExpression.java | 66 ++++++----- .../expression/WindowDefinition.java | 83 +++++++++++++ .../statement/select/PlainSelect.java | 51 +++++--- .../util/deparser/ExpressionDeParser.java | 110 +++++++++--------- .../util/deparser/SelectDeParser.java | 18 +-- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 52 ++++++--- .../statement/select/SelectTest.java | 10 ++ 8 files changed, 270 insertions(+), 122 deletions(-) create mode 100644 src/main/java/net/sf/jsqlparser/expression/WindowDefinition.java diff --git a/README.md b/README.md index c798cfa20..8d0541393 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ Any requests for examples or any particular documentation will be most welcome. ## Extensions in the latest SNAPSHOT version 4.6 +* support for named windows in window expressions: `SELECT sum(c) OVER winName FROM mytable WINDOW winName AS (PARTITION BY pcol)` + Additionally, we have fixed many errors and improved the code quality and the test coverage. ## Extensions of JSqlParser releases diff --git a/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java b/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java index ce4885045..3d1175493 100644 --- a/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java @@ -16,17 +16,14 @@ import net.sf.jsqlparser.statement.select.OrderByElement; /** - * Analytic function. The name of the function is variable but the parameters following the special - * analytic function path. e.g. row_number() over (order by test). Additional there can be an - * expression for an analytical aggregate like sum(col) or the "all collumns" wildcard like - * count(*). + * Analytic function. The name of the function is variable but the parameters following the special analytic function + * path. e.g. row_number() over (order by test). Additional there can be an expression for an analytical aggregate like + * sum(col) or the "all collumns" wildcard like count(*). * * @author tw */ public class AnalyticExpression extends ASTNodeAccessImpl implements Expression { - private final OrderByClause orderBy = new OrderByClause(); - private final PartitionByClause partitionBy = new PartitionByClause(); private String name; private Expression expression; private Expression offset; @@ -39,8 +36,9 @@ public class AnalyticExpression extends ASTNodeAccessImpl implements Expression private boolean ignoreNulls = false; //IGNORE NULLS inside function parameters private boolean ignoreNullsOutside = false; //IGNORE NULLS outside function parameters private Expression filterExpression = null; - private WindowElement windowElement = null; private List funcOrderBy = null; + private String windowName = null; // refers to an external window definition (paritionBy, orderBy, windowElement) + private WindowDefinition windowDef = new WindowDefinition(); public AnalyticExpression() { } @@ -76,11 +74,11 @@ public void accept(ExpressionVisitor expressionVisitor) { } public List getOrderByElements() { - return orderBy.getOrderByElements(); + return windowDef.orderBy.getOrderByElements(); } public void setOrderByElements(List orderByElements) { - orderBy.setOrderByElements(orderByElements); + windowDef.orderBy.setOrderByElements(orderByElements); } public KeepExpression getKeep() { @@ -92,7 +90,7 @@ public void setKeep(KeepExpression keep) { } public ExpressionList getPartitionExpressionList() { - return partitionBy.getPartitionExpressionList(); + return windowDef.partitionBy.getPartitionExpressionList(); } public void setPartitionExpressionList(ExpressionList partitionExpressionList) { @@ -100,11 +98,11 @@ public void setPartitionExpressionList(ExpressionList partitionExpressionList) { } public void setPartitionExpressionList(ExpressionList partitionExpressionList, boolean brackets) { - partitionBy.setPartitionExpressionList(partitionExpressionList, brackets); + windowDef.partitionBy.setPartitionExpressionList(partitionExpressionList, brackets); } public boolean isPartitionByBrackets() { - return partitionBy.isBrackets(); + return windowDef.partitionBy.isBrackets(); } public String getName() { @@ -140,11 +138,11 @@ public void setDefaultValue(Expression defaultValue) { } public WindowElement getWindowElement() { - return windowElement; + return windowDef.windowElement; } public void setWindowElement(WindowElement windowElement) { - this.windowElement = windowElement; + windowDef.windowElement = windowElement; } public AnalyticType getType() { @@ -187,6 +185,22 @@ public void setIgnoreNullsOutside(boolean ignoreNullsOutside) { this.ignoreNullsOutside = ignoreNullsOutside; } + public String getWindowName() { + return windowName; + } + + public void setWindowName(String windowName) { + this.windowName = windowName; + } + + public WindowDefinition getWindowDefinition() { + return windowDef; + } + + public void setWindowDefinition(WindowDefinition windowDef) { + this.windowDef = windowDef; + } + @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.MissingBreakInSwitch"}) public String toString() { @@ -210,11 +224,11 @@ public String toString() { if (isIgnoreNulls()) { b.append(" IGNORE NULLS"); } - if (funcOrderBy!=null) { + if (funcOrderBy != null) { b.append(" ORDER BY "); - b.append( funcOrderBy.stream().map(OrderByElement::toString).collect(joining(", "))); + b.append(funcOrderBy.stream().map(OrderByElement::toString).collect(joining(", "))); } - + b.append(") "); if (keep != null) { b.append(keep.toString()).append(" "); @@ -232,7 +246,7 @@ public String toString() { if (isIgnoreNullsOutside()) { b.append("IGNORE NULLS "); } - + switch (type) { case FILTER_ONLY: return b.toString(); @@ -242,20 +256,14 @@ public String toString() { default: b.append("OVER"); } - b.append(" ("); - partitionBy.toStringPartitionBy(b); - orderBy.toStringOrderByElements(b); - - if (windowElement != null) { - if (orderBy.getOrderByElements() != null) { - b.append(' '); - } - b.append(windowElement); + if (windowName != null) { + b.append(" ").append(windowName); + } else { + b.append(" "); + b.append(windowDef.toString()); } - b.append(")"); - return b.toString(); } diff --git a/src/main/java/net/sf/jsqlparser/expression/WindowDefinition.java b/src/main/java/net/sf/jsqlparser/expression/WindowDefinition.java new file mode 100644 index 000000000..434573b7a --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/WindowDefinition.java @@ -0,0 +1,83 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2022 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import java.util.List; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.statement.select.OrderByElement; + +public class WindowDefinition { + + final OrderByClause orderBy = new OrderByClause(); + final PartitionByClause partitionBy = new PartitionByClause(); + WindowElement windowElement = null; + private String windowName; + + public WindowElement getWindowElement() { + return windowElement; + } + + public void setWindowElement(WindowElement windowElement) { + this.windowElement = windowElement; + } + + public List getOrderByElements() { + return orderBy.getOrderByElements(); + } + + public void setOrderByElements(List orderByElements) { + orderBy.setOrderByElements(orderByElements); + } + + public ExpressionList getPartitionExpressionList() { + return partitionBy.getPartitionExpressionList(); + } + + public void setPartitionExpressionList(ExpressionList partitionExpressionList) { + setPartitionExpressionList(partitionExpressionList, false); + } + + public void setPartitionExpressionList(ExpressionList partitionExpressionList, boolean brackets) { + partitionBy.setPartitionExpressionList(partitionExpressionList, brackets); + } + + public String getWindowName() { + return windowName; + } + + public void setWindowName(String windowName) { + this.windowName = windowName; + } + + public WindowDefinition withWindowName(String windowName) { + setWindowName(windowName); + return this; + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + if (windowName != null) { + b.append(windowName).append(" AS "); + } + b.append("("); + partitionBy.toStringPartitionBy(b); + orderBy.toStringOrderByElements(b); + + if (windowElement != null) { + if (orderBy.getOrderByElements() != null) { + b.append(' '); + } + b.append(windowElement); + } + b.append(")"); + return b.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java index 6fc59b926..edc36af79 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java @@ -15,9 +15,11 @@ import java.util.Iterator; import java.util.List; import java.util.Optional; +import static java.util.stream.Collectors.joining; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.OracleHierarchicalExpression; import net.sf.jsqlparser.expression.OracleHint; +import net.sf.jsqlparser.expression.WindowDefinition; import net.sf.jsqlparser.parser.ASTNodeAccessImpl; import net.sf.jsqlparser.schema.Table; @@ -55,6 +57,7 @@ public class PlainSelect extends ASTNodeAccessImpl implements SelectBody { private boolean noWait = false; private boolean emitChanges = false; private WithIsolation withIsolation; + private List windowDefinitions; public boolean isUseBrackets() { return useBrackets; @@ -230,8 +233,7 @@ public void setHaving(Expression expression) { } /** - * A list of {@link Expression}s of the GROUP BY clause. It is null in case - * there is no GROUP BY clause + * A list of {@link Expression}s of the GROUP BY clause. It is null in case there is no GROUP BY clause * * @return a list of {@link Expression}s */ @@ -331,7 +333,6 @@ public boolean isEmitChanges() { return emitChanges; } - public WithIsolation getWithIsolation() { return withIsolation; } @@ -340,8 +341,16 @@ public void setWithIsolation(WithIsolation withIsolation) { this.withIsolation = withIsolation; } + public List getWindowDefinitions() { + return windowDefinitions; + } + + public void setWindowDefinitions(List windowDefinitions) { + this.windowDefinitions = windowDefinitions; + } + @Override - @SuppressWarnings({"PMD.CyclomaticComplexity" , "PMD.ExcessiveMethodLength", "PMD.NPathComplexity"}) + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength", "PMD.NPathComplexity"}) public String toString() { StringBuilder sql = new StringBuilder(); if (useBrackets) { @@ -418,8 +427,14 @@ public String toString() { if (having != null) { sql.append(" HAVING ").append(having); } + + if (windowDefinitions != null) { + sql.append(" WINDOW "); + sql.append(windowDefinitions.stream().map(WindowDefinition::toString).collect(joining(", "))); + } + sql.append(orderByToString(oracleSiblings, orderByElements)); - if (emitChanges){ + if (emitChanges) { sql.append(" EMIT CHANGES"); } if (limit != null) { @@ -471,7 +486,7 @@ public String toString() { } if (withIsolation != null) { sql.append(withIsolation); - } + } } if (forXmlPath != null) { sql.append(" FOR XML PATH(").append(forXmlPath).append(")"); @@ -509,8 +524,8 @@ public static String getFormatedList(List list, String expression, boolean us } /** - * List the toString out put of the objects in the List comma separated. If the - * List is null or empty an empty string is returned. + * List the toString out put of the objects in the List comma separated. If the List is null or empty an empty + * string is returned. * * The same as getStringList(list, true, false) * @@ -523,11 +538,11 @@ public static String getStringList(List list) { } /** - * List the toString out put of the objects in the List that can be comma - * separated. If the List is null or empty an empty string is returned. + * List the toString out put of the objects in the List that can be comma separated. If the List is null or empty an + * empty string is returned. * - * @param list list of objects with toString methods - * @param useComma true if the list has to be comma separated + * @param list list of objects with toString methods + * @param useComma true if the list has to be comma separated * @param useBrackets true if the list has to be enclosed in brackets * @return comma separated list of the elements in the list */ @@ -536,11 +551,11 @@ public static String getStringList(List list, boolean useComma, boolean useBr } /** - * Append the toString out put of the objects in the List (that can be comma - * separated). If the List is null or empty an empty string is returned. + * Append the toString out put of the objects in the List (that can be comma separated). If the List is null or + * empty an empty string is returned. * - * @param list list of objects with toString methods - * @param useComma true if the list has to be comma separated + * @param list list of objects with toString methods + * @param useComma true if the list has to be comma separated * @param useBrackets true if the list has to be enclosed in brackets * @return comma separated list of the elements in the list */ @@ -554,9 +569,9 @@ public static StringBuilder appendStringListTo(StringBuilder builder, List li int size = list.size(); for (int i = 0; i < size; i++) { - builder.append(list.get(i)).append( i < size - 1 + builder.append(list.get(i)).append(i < size - 1 ? comma + " " - : "" ); + : ""); } if (useBrackets) { 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 0cef345fe..5b33fab0c 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -324,6 +324,7 @@ public void visit(OrExpression orExpression) { visitBinaryExpression(orExpression, " OR "); } + @Override public void visit(XorExpression xorExpression) { visitBinaryExpression(xorExpression, " XOR "); @@ -550,7 +551,7 @@ public void visit(WhenClause whenClause) { public void visit(AnyComparisonExpression anyComparisonExpression) { buffer.append(anyComparisonExpression.getAnyType().name()).append(" ( "); SubSelect subSelect = anyComparisonExpression.getSubSelect(); - if (subSelect!=null) { + if (subSelect != null) { subSelect.accept((ExpressionVisitor) this); } else { ExpressionList expressionList = (ExpressionList) anyComparisonExpression.getItemsList(); @@ -558,7 +559,7 @@ public void visit(AnyComparisonExpression anyComparisonExpression) { buffer.append( PlainSelect.getStringList(expressionList.getExpressions(), true, anyComparisonExpression.isUsingBracketsForValues())); } - buffer.append(" ) "); + buffer.append(" ) "); } @Override @@ -592,7 +593,7 @@ public void visit(CastExpression cast) { buffer.append("CAST("); cast.getLeftExpression().accept(this); buffer.append(" AS "); - buffer.append( cast.getRowConstructor()!=null ? cast.getRowConstructor() : cast.getType() ); + buffer.append(cast.getRowConstructor() != null ? cast.getRowConstructor() : cast.getType()); buffer.append(")"); } else { cast.getLeftExpression().accept(this); @@ -607,7 +608,7 @@ public void visit(TryCastExpression cast) { buffer.append("TRY_CAST("); cast.getLeftExpression().accept(this); buffer.append(" AS "); - buffer.append( cast.getRowConstructor()!=null ? cast.getRowConstructor() : cast.getType() ); + buffer.append(cast.getRowConstructor() != null ? cast.getRowConstructor() : cast.getType()); buffer.append(")"); } else { cast.getLeftExpression().accept(this); @@ -659,9 +660,9 @@ public void visit(AnalyticExpression aexpr) { } if (aexpr.getFuncOrderBy() != null) { buffer.append(" ORDER BY "); - buffer.append( aexpr.getFuncOrderBy().stream().map(OrderByElement::toString).collect(joining(", "))); + buffer.append(aexpr.getFuncOrderBy().stream().map(OrderByElement::toString).collect(joining(", "))); } - + buffer.append(") "); if (keep != null) { keep.accept(this); @@ -676,7 +677,7 @@ public void visit(AnalyticExpression aexpr) { buffer.append(" "); } } - + if (aexpr.isIgnoreNullsOutside()) { buffer.append("IGNORE NULLS "); } @@ -690,45 +691,50 @@ public void visit(AnalyticExpression aexpr) { default: buffer.append("OVER"); } - buffer.append(" ("); - if (partitionExpressionList != null && !partitionExpressionList.getExpressions().isEmpty()) { - buffer.append("PARTITION BY "); - if (aexpr.isPartitionByBrackets()) { - buffer.append("("); - } - List expressions = partitionExpressionList.getExpressions(); - for (int i = 0; i < expressions.size(); i++) { - if (i > 0) { - buffer.append(", "); + if (aexpr.getWindowName() != null) { + buffer.append(" ").append(aexpr.getWindowName()); + } else { + buffer.append(" ("); + + if (partitionExpressionList != null && !partitionExpressionList.getExpressions().isEmpty()) { + buffer.append("PARTITION BY "); + if (aexpr.isPartitionByBrackets()) { + buffer.append("("); } - expressions.get(i).accept(this); - } - if (aexpr.isPartitionByBrackets()) { - buffer.append(")"); + List expressions = partitionExpressionList.getExpressions(); + for (int i = 0; i < expressions.size(); i++) { + if (i > 0) { + buffer.append(", "); + } + expressions.get(i).accept(this); + } + if (aexpr.isPartitionByBrackets()) { + buffer.append(")"); + } + buffer.append(" "); } - buffer.append(" "); - } - if (orderByElements != null && !orderByElements.isEmpty()) { - buffer.append("ORDER BY "); - orderByDeParser.setExpressionVisitor(this); - orderByDeParser.setBuffer(buffer); - for (int i = 0; i < orderByElements.size(); i++) { - if (i > 0) { - buffer.append(", "); + if (orderByElements != null && !orderByElements.isEmpty()) { + buffer.append("ORDER BY "); + orderByDeParser.setExpressionVisitor(this); + orderByDeParser.setBuffer(buffer); + for (int i = 0; i < orderByElements.size(); i++) { + if (i > 0) { + buffer.append(", "); + } + orderByDeParser.deParseElement(orderByElements.get(i)); } - orderByDeParser.deParseElement(orderByElements.get(i)); } - } - if (windowElement != null) { - if (orderByElements != null && !orderByElements.isEmpty()) { - buffer.append(' '); + if (windowElement != null) { + if (orderByElements != null && !orderByElements.isEmpty()) { + buffer.append(' '); + } + buffer.append(windowElement); } - buffer.append(windowElement); - } - buffer.append(")"); + buffer.append(")"); + } } @Override @@ -815,12 +821,12 @@ public void visit(RowConstructor rowConstructor) { buffer.append(rowConstructor.getName()); } buffer.append("("); - - if (rowConstructor.getColumnDefinitions().size()>0) { + + if (rowConstructor.getColumnDefinitions().size() > 0) { buffer.append("("); int i = 0; - for (ColumnDefinition columnDefinition:rowConstructor.getColumnDefinitions()) { - buffer.append(i>0 ? ", " : "").append(columnDefinition.toString()); + for (ColumnDefinition columnDefinition : rowConstructor.getColumnDefinitions()) { + buffer.append(i > 0 ? ", " : "").append(columnDefinition.toString()); i++; } buffer.append(")"); @@ -861,7 +867,7 @@ public void visit(DateTimeLiteralExpression literal) { @Override public void visit(NextValExpression nextVal) { - buffer.append(nextVal.isUsingNextValueFor() ? "NEXT VALUE FOR " : "NEXTVAL FOR ").append(nextVal.getName()); + buffer.append(nextVal.isUsingNextValueFor() ? "NEXT VALUE FOR " : "NEXTVAL FOR ").append(nextVal.getName()); } @Override @@ -929,7 +935,7 @@ public void visit(XMLSerializeExpr expr) { buffer.append("xmlserialize(xmlagg(xmltext("); expr.getExpression().accept(this); buffer.append(")"); - if (expr.getOrderByElements() != null){ + if (expr.getOrderByElements() != null) { buffer.append(" ORDER BY "); for (Iterator i = expr.getOrderByElements().iterator(); i.hasNext();) { buffer.append(i.next().toString()); @@ -958,9 +964,9 @@ public void visit(JsonAggregateFunction expression) { @Override public void visit(JsonFunction expression) { - expression.append(buffer); + expression.append(buffer); } - + @Override public void visit(ConnectByRootOperator connectByRootOperator) { buffer.append("CONNECT_BY_ROOT "); @@ -970,9 +976,9 @@ public void visit(ConnectByRootOperator connectByRootOperator) { @Override public void visit(OracleNamedFunctionParameter oracleNamedFunctionParameter) { buffer - .append(oracleNamedFunctionParameter.getName()) - .append(" => "); - + .append(oracleNamedFunctionParameter.getName()) + .append(" => "); + oracleNamedFunctionParameter.getExpression().accept(this); } @@ -993,9 +999,9 @@ public void visit(AllValue allValue) { @Override public void visit(IsDistinctExpression isDistinctExpression) { - buffer.append(isDistinctExpression.getLeftExpression() + - isDistinctExpression.getStringExpression() + - isDistinctExpression.getRightExpression()); + buffer.append(isDistinctExpression.getLeftExpression() + + isDistinctExpression.getStringExpression() + + isDistinctExpression.getRightExpression()); } @Override diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java index 7889a3369..a997c5056 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -11,6 +11,7 @@ import java.util.Iterator; import java.util.List; +import static java.util.stream.Collectors.joining; import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; @@ -182,12 +183,15 @@ public void visit(PlainSelect plainSelect) { buffer.append(" HAVING "); plainSelect.getHaving().accept(expressionVisitor); } - + if (plainSelect.getWindowDefinitions() != null) { + buffer.append(" WINDOW "); + buffer.append(plainSelect.getWindowDefinitions().stream().map(WindowDefinition::toString).collect(joining(", "))); + } if (plainSelect.getOrderByElements() != null) { new OrderByDeParser(expressionVisitor, buffer).deParse(plainSelect.isOracleSiblings(), plainSelect.getOrderByElements()); } - if (plainSelect.isEmitChanges()){ + if (plainSelect.isEmitChanges()) { buffer.append(" EMIT CHANGES"); } if (plainSelect.getLimit() != null) { @@ -224,7 +228,7 @@ public void visit(PlainSelect plainSelect) { if (plainSelect.isUseBrackets()) { buffer.append(")"); } - + } @Override @@ -318,9 +322,9 @@ public void visit(UnPivot unpivot) { .append(showOptions && includeNulls ? " INCLUDE NULLS" : "") .append(showOptions && !includeNulls ? " EXCLUDE NULLS" : "") .append(" (").append(PlainSelect.getStringList(unPivotClause, true, - unPivotClause != null && unPivotClause.size() > 1)) + unPivotClause != null && unPivotClause.size() > 1)) .append(" FOR ").append(PlainSelect.getStringList(unpivotForClause, true, - unpivotForClause != null && unpivotForClause.size() > 1)) + unpivotForClause != null && unpivotForClause.size() > 1)) .append(" IN ").append(PlainSelect.getStringList(unpivot.getUnPivotInClause(), true, true)).append(")"); if (unpivot.getAlias() != null) { buffer.append(unpivot.getAlias().toString()); @@ -443,7 +447,7 @@ public void deparseJoin(Join join) { buffer.append(" ON "); onExpression.accept(expressionVisitor); } - if (join.getUsingColumns().size()>0) { + if (join.getUsingColumns().size() > 0) { buffer.append(" USING ("); for (Iterator iterator = join.getUsingColumns().iterator(); iterator.hasNext();) { Column column = iterator.next(); @@ -546,7 +550,7 @@ public void visit(TableFunction tableFunction) { public void visit(ParenthesisFromItem parenthesis) { buffer.append("("); parenthesis.getFromItem().accept(this); - + buffer.append(")"); if (parenthesis.getAlias() != null) { buffer.append(parenthesis.getAlias().toString()); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 16cdb9469..d128da961 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -2007,6 +2007,8 @@ PlainSelect PlainSelect() #PlainSelect: Token token; KSQLWindow ksqlWindow = null; boolean noWait = false; + String windowName = null; + WindowDefinition winDef; } { @@ -2051,12 +2053,17 @@ PlainSelect PlainSelect() #PlainSelect: fromItem=FromItem() joins=JoinsList() ] - [ ksqlWindow=KSQLWindowClause() { plainSelect.setKsqlWindow(ksqlWindow); } ] + [ LOOKAHEAD(2) ksqlWindow=KSQLWindowClause() { plainSelect.setKsqlWindow(ksqlWindow); } ] [ LOOKAHEAD(2) where=WhereClause() { plainSelect.setWhere(where); }] [ oracleHierarchicalQueryClause=OracleHierarchicalQueryClause() { plainSelect.setOracleHierarchical(oracleHierarchicalQueryClause); } ] [ groupBy=GroupByColumnReferences() { plainSelect.setGroupByElement(groupBy); }] [ having=Having() { plainSelect.setHaving(having); }] [LOOKAHEAD( ) orderByElements = OrderByElements() { plainSelect.setOracleSiblings(true); plainSelect.setOrderByElements(orderByElements); } ] + [ + windowName = RelObjectName() winDef = windowDefinition() { List winDefs = new ArrayList(); winDefs.add(winDef.withWindowName(windowName)); } + ( LOOKAHEAD(2) "," windowName = RelObjectName() winDef = windowDefinition() { winDefs.add(winDef.withWindowName(windowName)); } )* + { plainSelect.setWindowDefinitions(winDefs); } + ] [LOOKAHEAD( ) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ] [ { plainSelect.setEmitChanges(true); } ] [LOOKAHEAD() limit = LimitWithOffset() { plainSelect.setLimit(limit); } ] @@ -4450,27 +4457,40 @@ KeepExpression KeepExpression() : { void windowFun(AnalyticExpression retval):{ - ExpressionList expressionList = null; - List olist = null; - WindowElement windowElement = null; - boolean partitionByBrackets = false; + String windowName = null; + WindowDefinition winDef; } { ([ { retval.setIgnoreNullsOutside(true); } ] {retval.setType(AnalyticType.OVER);} | {retval.setType(AnalyticType.WITHIN_GROUP);} ) + ( + windowName = RelObjectName() { retval.setWindowName(windowName); } + | + winDef = windowDefinition() { retval.setWindowDefinition(winDef); } + ) +} + +WindowDefinition windowDefinition() : { + ExpressionList expressionList = null; + List olist = null; + WindowElement windowElement = null; + boolean partitionByBrackets = false; + WindowDefinition winDef = new WindowDefinition(); +} { "(" - [ - (LOOKAHEAD(ComplexExpressionList()) expressionList=ComplexExpressionList() - | "(" {partitionByBrackets = true;} expressionList=ComplexExpressionList() ")" ) - ] - [olist=OrderByElements() ] - [windowElement = WindowElement() ] - { - retval.setPartitionExpressionList(expressionList, partitionByBrackets); - retval.setOrderByElements(olist); - retval.setWindowElement(windowElement); - } + [ + (LOOKAHEAD(ComplexExpressionList()) expressionList=ComplexExpressionList() + | "(" {partitionByBrackets = true;} expressionList=ComplexExpressionList() ")" ) + ] + [olist=OrderByElements() ] + [windowElement = WindowElement() ] + { + winDef.setPartitionExpressionList(expressionList, partitionByBrackets); + winDef.setOrderByElements(olist); + winDef.setWindowElement(windowElement); + } ")" + { return winDef; } } AnalyticExpression AnalyticExpression(Function function) : diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index 14536699e..e940e51f6 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -5232,4 +5232,14 @@ public void testPostgresNaturalJoinIssue1559() throws JSQLParserException { "FROM table1 as t1\n" + "NATURAL RIGHT JOIN table2 as t2", true); } + + @Test + public void testNamedWindowDefinitionIssue1581() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT sum(salary) OVER w, avg(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary DESC)"); + } + + @Test + public void testNamedWindowDefinitionIssue1581_2() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT sum(salary) OVER w1, avg(salary) OVER w2 FROM empsalary WINDOW w1 AS (PARTITION BY depname ORDER BY salary DESC), w2 AS (PARTITION BY depname2 ORDER BY salary2)"); + } }