diff --git a/pmd-plsql/etc/grammar/PldocAST.jjt b/pmd-plsql/etc/grammar/PldocAST.jjt index 8ef66b2b11..a50c8633b8 100644 --- a/pmd-plsql/etc/grammar/PldocAST.jjt +++ b/pmd-plsql/etc/grammar/PldocAST.jjt @@ -1561,6 +1561,19 @@ ASTWindowingClause WindowingClause() : { return jjtThis; } } +/** + * Within Clause is used for the following analytic functions: CUME_DIST, DENSE_RANK, LISTAGG, PERCENT_RANK, PERCENTILE_CONT, + * PERCENTILE_DISC, RANK. + * + * See e.g. https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/LISTAGG.html + */ +ASTWithinClause WithinClause() : +{} +{ + "WITHIN" "(" OrderByClause() ")" [ LOOKAHEAD(2) "OVER" "(" QueryPartitionClause() ")" ] + { return jjtThis; } +} + ASTColumn Column() : { ASTID id; } { @@ -2950,8 +2963,8 @@ ASTPrimaryPrefix PrimaryPrefix() : } { ( - // Note: AnalyticClause is only allowed for specific functions, but this grammar allows it for all functions. - LOOKAHEAD(FunctionName() "(") simpleNode = FunctionCall() [ AnalyticClause() ] + // Note: AnalyticClause and WithinClause are only allowed for specific functions, but this grammar allows it for all functions. + LOOKAHEAD(FunctionName() "(") simpleNode = FunctionCall() [ AnalyticClause() ] [ WithinClause() ] | LOOKAHEAD(Literal()) simpleNode = Literal() { sb.append(simpleNode.getImage()) ; } | LOOKAHEAD(MultiSetCondition()) simpleNode = MultiSetCondition() | LOOKAHEAD(TrimExpression()) simpleNode = TrimExpression() //SRT 20110613.3 @@ -4810,7 +4823,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} { // PL/SQL UNRESERVED KEYWORDS - V$RESERVED.RESERVED='N' ( -"FIRST" | "REPLACE" | "REF" | "LAST" | "TRIM" | "OVER" | "UNBOUNDED" | "PRECEDING" | "FOLLOWING" | +"FIRST" | "REPLACE" | "REF" | "LAST" | "TRIM" | "OVER" | "UNBOUNDED" | "PRECEDING" | "FOLLOWING" | "WITHIN" | | | @@ -5974,7 +5987,6 @@ ASTID ID(): {} // | | // this causes bug 643043 Procedure w/o params appears as variable | //SYNTAX - //| //RESERVED WORD | //RESERVED WORD //20120501 | | //SYNTAX @@ -6221,7 +6233,6 @@ ASTQualifiedID QualifiedID(): {} // //| // - // // //20120501 | // diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java index d016edaa1f..4d1815fe7f 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java @@ -1036,4 +1036,9 @@ public class PLSQLParserVisitorAdapter implements PLSQLParserVisitor { public Object visit(ASTWindowingClause node, Object data) { return visit((PLSQLNode) node, data); } + + @Override + public Object visit(ASTWithinClause node, Object data) { + return visit((PLSQLNode) node, data); + } } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java index 6057471142..dd57566cbf 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java @@ -1130,6 +1130,11 @@ public abstract class AbstractPLSQLRule extends AbstractRule implements PLSQLPar return visit((PLSQLNode) node, data); } + @Override + public Object visit(ASTWithinClause node, Object data) { + return visit((PLSQLNode) node, data); + } + /* * Treat all Executable Code */ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls index 5d7185bf9a..add6138f82 100644 --- a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls @@ -25,5 +25,20 @@ SELECT manager_id, last_name, hire_date, salary, FROM employees ORDER BY manager_id, hire_date, salary; +-- Example from: https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/LISTAGG.html +SELECT LISTAGG(last_name, '; ') + WITHIN GROUP (ORDER BY hire_date, last_name) "Emp_list", + MIN(hire_date) "Earliest" + INTO some_record + FROM employees + WHERE department_id = 30; + +SELECT department_id "Dept.", + LISTAGG(last_name, '; ') WITHIN GROUP (ORDER BY hire_date) "Employees" + INTO some_record + FROM employees + GROUP BY department_id + ORDER BY department_id; + END; / \ No newline at end of file