From 06eb3e24ebef27b231cf6caa240cc5e235f649ad Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 24 Jan 2019 18:53:14 +0100 Subject: [PATCH 1/3] [plsql] Parse Exception with EXISTS Fixes #1587 --- docs/pages/release_notes.md | 2 ++ pmd-plsql/etc/grammar/PldocAST.jjt | 12 +++++++++- .../plsql/ast/PLSQLParserVisitorAdapter.java | 5 ++++ .../lang/plsql/rule/AbstractPLSQLRule.java | 5 ++++ .../pmd/lang/plsql/ast/WhereClauseTest.java | 7 ++++++ .../pmd/lang/plsql/ast/WhereClauseExists.pls | 24 +++++++++++++++++++ 6 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseExists.pls diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index ee410dcd04..51de4955c2 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -37,6 +37,8 @@ This is a {{ site.pmd.release_type }} release. * [#1633](https://github.com/pmd/pmd/issues/1633): \[java] UnsynchronizedStaticFormatter reports commons lang FastDateFormat * java-performance * [#1632](https://github.com/pmd/pmd/issues/1632): \[java] ConsecutiveLiteralAppends false positive over catch +* plsql + * [#1587](https://github.com/pmd/pmd/issues/1587): \[plsql] Parse Exception with EXISTS ### API Changes diff --git a/pmd-plsql/etc/grammar/PldocAST.jjt b/pmd-plsql/etc/grammar/PldocAST.jjt index f08f27c0cc..97d3b6e4c4 100644 --- a/pmd-plsql/etc/grammar/PldocAST.jjt +++ b/pmd-plsql/etc/grammar/PldocAST.jjt @@ -1314,6 +1314,8 @@ void Condition2() #void : LOOKAHEAD(4) IsNullCondition() | LOOKAHEAD(4) IsOfTypeCondition() + | + ExistsCondition() ) } @@ -1338,6 +1340,14 @@ ASTLikeCondition LikeCondition() : { return jjtThis; } } +ASTExistsCondition ExistsCondition() : +{} +{ + "(" Subquery() ")" + + { return jjtThis; } +} + ASTCompoundCondition CompoundCondition() : {} { @@ -5848,7 +5858,7 @@ ASTID ID(): {} | //SYNTAX | //SYNTAX //RESERVED WORD | //SYNTAX - | //SYNTAX //RESERVED WORD + //| //SYNTAX //RESERVED WORD //| //SYNTAX //20120501 | | //SYNTAX 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 9d86b884b8..82887774e0 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 @@ -986,4 +986,9 @@ public class PLSQLParserVisitorAdapter implements PLSQLParserVisitor { public Object visit(ASTValuesClause node, Object data) { return visit((PLSQLNode) node, data); } + + @Override + public Object visit(ASTExistsCondition 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 f43818e62d..2bbc0f5b03 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 @@ -1080,6 +1080,11 @@ public abstract class AbstractPLSQLRule extends AbstractRule implements PLSQLPar return visit((PLSQLNode) node, data); } + @Override + public Object visit(ASTExistsCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + /* * Treat all Executable Code */ diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java index 18d549472a..7524bcea6f 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java @@ -68,4 +68,11 @@ public class WhereClauseTest extends AbstractPLSQLParserTst { StandardCharsets.UTF_8); ASTInput input = parsePLSQL(code); } + + @Test + public void testExistsCondition() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("WhereClauseExists.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } } diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseExists.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseExists.pls new file mode 100644 index 0000000000..e300dfb7a0 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseExists.pls @@ -0,0 +1,24 @@ +-- +-- Where Clause With Exists Condition +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/EXISTS-Condition.html#GUID-20259A83-C42B-4E0D-8DF4-9A2A66ACA8E7 +-- + +BEGIN + +SELECT id + INTO id_out + FROM some_table + WHERE EXISTS + (SELECT NULL + FROM other_table + WHERE other_id = other_id_in); + +DELETE FROM some_table + WHERE id = id_in + AND NOT EXISTS + (SELECT NULL + FROM other_table + WHERE id = id_in); + +END; +/ From 2e07139f8d3b8e7face847c87713259c49c45481 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 24 Jan 2019 19:23:13 +0100 Subject: [PATCH 2/3] [plsql] Add multiset condition in where clause --- pmd-plsql/etc/grammar/PldocAST.jjt | 44 ++++++++++++++++++- .../plsql/ast/PLSQLParserVisitorAdapter.java | 20 +++++++++ .../lang/plsql/rule/AbstractPLSQLRule.java | 20 +++++++++ .../pmd/lang/plsql/ast/WhereClauseTest.java | 7 +++ .../lang/plsql/ast/WhereClauseMultiset.pls | 35 +++++++++++++++ 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseMultiset.pls diff --git a/pmd-plsql/etc/grammar/PldocAST.jjt b/pmd-plsql/etc/grammar/PldocAST.jjt index 97d3b6e4c4..c61770b02d 100644 --- a/pmd-plsql/etc/grammar/PldocAST.jjt +++ b/pmd-plsql/etc/grammar/PldocAST.jjt @@ -1300,6 +1300,8 @@ void Condition2() #void : ( // a IS OF Type condition that starts with a function... LOOKAHEAD(FunctionCall() ) IsOfTypeCondition() + | + LOOKAHEAD(9) MultisetCondition() | LOOKAHEAD(9) ComparisonCondition() | @@ -1348,6 +1350,46 @@ ASTExistsCondition ExistsCondition() : { return jjtThis; } } +void MultisetCondition() #void : +{} +{ + LOOKAHEAD(4) IsASetCondition() + | + LOOKAHEAD(4) IsEmptyCondition() + | + LOOKAHEAD(4) MemberCondition() + | + LOOKAHEAD(4) SubmultisetCondition() +} + +ASTIsASetCondition IsASetCondition() : +{} +{ + TableName() [] + { return jjtThis; } +} + +ASTIsEmptyCondition IsEmptyCondition() : +{} +{ + TableName() [] + { return jjtThis; } +} + +ASTMemberCondition MemberCondition() : +{} +{ + SqlExpression() [] [] TableName() + { return jjtThis; } +} + +ASTSubmultisetCondition SubmultisetCondition() : +{} +{ + TableName() [] [] TableName() + { return jjtThis; } +} + ASTCompoundCondition CompoundCondition() : {} { @@ -5904,7 +5946,7 @@ ASTID ID(): {} | //RESERVED WORD | | - | //RESERVED WORD + //| //RESERVED WORD //| //RESERVED WORD //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 82887774e0..770b089c2a 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 @@ -991,4 +991,24 @@ public class PLSQLParserVisitorAdapter implements PLSQLParserVisitor { public Object visit(ASTExistsCondition node, Object data) { return visit((PLSQLNode) node, data); } + + @Override + public Object visit(ASTIsASetCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTIsEmptyCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTMemberCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSubmultisetCondition 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 2bbc0f5b03..66f58346c7 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 @@ -1085,6 +1085,26 @@ public abstract class AbstractPLSQLRule extends AbstractRule implements PLSQLPar return visit((PLSQLNode) node, data); } + @Override + public Object visit(ASTIsASetCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTIsEmptyCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTMemberCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSubmultisetCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + /* * Treat all Executable Code */ diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java index 7524bcea6f..6bd7a0241c 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java @@ -75,4 +75,11 @@ public class WhereClauseTest extends AbstractPLSQLParserTst { StandardCharsets.UTF_8); ASTInput input = parsePLSQL(code); } + + @Test + public void testMultisetCondition() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("WhereClauseMultiset.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } } diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseMultiset.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseMultiset.pls new file mode 100644 index 0000000000..29da118eb9 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseMultiset.pls @@ -0,0 +1,35 @@ +-- +-- Where Clause With Multiset Conditions +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/Multiset-Conditions.html#GUID-E8164A15-715A-40A0-944D-26DF4C84DE3F +-- + +BEGIN + +SELECT customer_id, cust_address_ntab + INTO test + FROM customers_demo + WHERE cust_address_ntab IS A SET + ORDER BY customer_id; + +SELECT product_id, TO_CHAR(ad_finaltext) AS text + INTO test + FROM print_media + WHERE ad_textdocs_ntab IS NOT EMPTY + ORDER BY product_id, text; + +SELECT customer_id, cust_address_ntab + INTO test + FROM customers_demo + WHERE cust_address_typ('8768 N State Rd 37', 47404, + 'Bloomington', 'IN', 'US') + MEMBER OF cust_address_ntab + ORDER BY customer_id; + +SELECT customer_id, cust_address_ntab + INTO test + FROM customers_demo + WHERE cust_address_ntab SUBMULTISET OF cust_address2_ntab + ORDER BY customer_id; + +END; +/ From b32e5b09108a54e4bc2e6dd8c5bcf3c1c98ecb84 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 24 Jan 2019 20:14:06 +0100 Subject: [PATCH 3/3] [plsql] Support REGEXP_LIKE condition --- pmd-plsql/etc/grammar/PldocAST.jjt | 18 ++++++++- pmd-plsql/src/main/ant/alljavacc.xml | 1 + .../plsql/ast/ASTRegexpLikeCondition.java | 40 +++++++++++++++++++ .../plsql/ast/PLSQLParserVisitorAdapter.java | 5 +++ .../lang/plsql/rule/AbstractPLSQLRule.java | 5 +++ .../pmd/lang/plsql/ast/WhereClauseTest.java | 12 ++++++ .../lang/plsql/ast/WhereClauseRegexpLike.pls | 20 ++++++++++ 7 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTRegexpLikeCondition.java create mode 100644 pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseRegexpLike.pls diff --git a/pmd-plsql/etc/grammar/PldocAST.jjt b/pmd-plsql/etc/grammar/PldocAST.jjt index c61770b02d..9d7327454c 100644 --- a/pmd-plsql/etc/grammar/PldocAST.jjt +++ b/pmd-plsql/etc/grammar/PldocAST.jjt @@ -1310,6 +1310,8 @@ void Condition2() #void : LOOKAHEAD(9) InCondition() | LOOKAHEAD(9) LikeCondition() + | + LOOKAHEAD(9) RegexpLikeCondition() | LOOKAHEAD(4) BetweenCondition() | @@ -1342,6 +1344,19 @@ ASTLikeCondition LikeCondition() : { return jjtThis; } } +ASTRegexpLikeCondition RegexpLikeCondition() : +{ + Token matchParam = null; +} +{ + "(" + ID() "," + StringLiteral() + [ "," ( matchParam = | matchParam = ) {jjtThis.setMatchParam(matchParam.toString());} ] + ")" + { return jjtThis; } +} + ASTExistsCondition ExistsCondition() : {} { @@ -4365,6 +4380,7 @@ TOKEN [IGNORE_CASE]: | | | + | | | | //SRT 2011-04-17 @@ -5523,7 +5539,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| //| -//| +| //| //| //| diff --git a/pmd-plsql/src/main/ant/alljavacc.xml b/pmd-plsql/src/main/ant/alljavacc.xml index dbd24f2f2c..3f934511da 100644 --- a/pmd-plsql/src/main/ant/alljavacc.xml +++ b/pmd-plsql/src/main/ant/alljavacc.xml @@ -62,6 +62,7 @@ + diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTRegexpLikeCondition.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTRegexpLikeCondition.java new file mode 100644 index 0000000000..cb4f588c82 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTRegexpLikeCondition.java @@ -0,0 +1,40 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +public class ASTRegexpLikeCondition extends net.sourceforge.pmd.lang.plsql.ast.AbstractPLSQLNode { + private String matchParam; + + public ASTRegexpLikeCondition(int id) { + super(id); + } + + public ASTRegexpLikeCondition(PLSQLParser p, int id) { + super(p, id); + } + + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + void setMatchParam(String matchParam) { + this.matchParam = matchParam; + } + + public String getMatchParam() { + return this.matchParam; + } + + public ASTID getSourceChar() { + return (ASTID) jjtGetChild(0); + } + + public ASTStringLiteral getPattern() { + return (ASTStringLiteral) jjtGetChild(1); + } +} +/* JavaCC - OriginalChecksum=afb8806a0c67f95b736d6e8bc46def15 (do not edit this line) */ 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 770b089c2a..4c6e564504 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 @@ -1011,4 +1011,9 @@ public class PLSQLParserVisitorAdapter implements PLSQLParserVisitor { public Object visit(ASTSubmultisetCondition node, Object data) { return visit((PLSQLNode) node, data); } + + @Override + public Object visit(ASTRegexpLikeCondition 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 66f58346c7..89da4e2582 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 @@ -1105,6 +1105,11 @@ public abstract class AbstractPLSQLRule extends AbstractRule implements PLSQLPar return visit((PLSQLNode) node, data); } + @Override + public Object visit(ASTRegexpLikeCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + /* * Treat all Executable Code */ diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java index 6bd7a0241c..98ef0c72ff 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java @@ -82,4 +82,16 @@ public class WhereClauseTest extends AbstractPLSQLParserTst { StandardCharsets.UTF_8); ASTInput input = parsePLSQL(code); } + + @Test + public void testRegexpLikeCondition() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("WhereClauseRegexpLike.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + List regexps = input.findDescendantsOfType(ASTRegexpLikeCondition.class); + Assert.assertEquals(2, regexps.size()); + Assert.assertEquals("last_name", regexps.get(1).getSourceChar().getImage()); + Assert.assertEquals("'([aeiou])\\1'", regexps.get(1).getPattern().getImage()); + Assert.assertEquals("'i'", regexps.get(1).getMatchParam()); + } } diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseRegexpLike.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseRegexpLike.pls new file mode 100644 index 0000000000..7345ee5ad0 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseRegexpLike.pls @@ -0,0 +1,20 @@ +-- +-- Where Clause With Regexp Like +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/Pattern-matching-Conditions.html#GUID-D2124F3A-C6E4-4CCA-A40E-2FFCABFD8E19 +-- + +BEGIN + +SELECT first_name, last_name +INTO test +FROM employees +WHERE REGEXP_LIKE (first_name, '^Ste(v|ph)en$') +ORDER BY first_name, last_name; + +SELECT last_name +INTO test +FROM employees +WHERE REGEXP_LIKE (last_name, '([aeiou])\1', 'i') +ORDER BY last_name; +END; +/