From 7a57aeb4dbf2f0fcb9af997689ea471d0a748b32 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 7 Jun 2024 17:38:18 +0200 Subject: [PATCH] [plsql] Support exception handlers in compound triggers - allow multiple exception handlers - parse declarative section correctly in compound triggers - CASE, CURSOR, DECLARE, EXECUTE, IF, PUBLIC, SQL are reserved words and cannot be used as an identifiers Fixes #4270 --- docs/pages/release_notes.md | 1 + pmd-plsql/etc/grammar/PLSQL.jjt | 215 +++++++--------- .../pmd/lang/plsql/ast/PlsqlTreeDumpTest.java | 5 + ...dTriggerWithAdditionalDeclarations4270.pls | 45 ++++ ...dTriggerWithAdditionalDeclarations4270.txt | 239 ++++++++++++++++++ .../sourceforge/pmd/lang/plsql/ast/Using.pls | 8 +- .../design/xml/ExcessiveParameterList.xml | 4 - .../plsql/rule/design/xml/TooManyFields.xml | 28 +- 8 files changed, 386 insertions(+), 159 deletions(-) create mode 100644 pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CompoundTriggerWithAdditionalDeclarations4270.pls create mode 100644 pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CompoundTriggerWithAdditionalDeclarations4270.txt diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 71b7e5b641..f89d82557d 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -20,6 +20,7 @@ This is a {{ site.pmd.release_type }} release. * plsql * [#1934](https://github.com/pmd/pmd/issues/1934): \[plsql] ParseException with MERGE statement in anonymous block * [#2779](https://github.com/pmd/pmd/issues/2779): \[plsql] Error while parsing statement with (Oracle) DML Error Logging + * [#4270](https://github.com/pmd/pmd/issues/4270): \[plsql] Parsing exception COMPOUND TRIGGER with EXCEPTION handler ### 🚨 API Changes diff --git a/pmd-plsql/etc/grammar/PLSQL.jjt b/pmd-plsql/etc/grammar/PLSQL.jjt index 0bfb7feaac..9c66c4895f 100644 --- a/pmd-plsql/etc/grammar/PLSQL.jjt +++ b/pmd-plsql/etc/grammar/PLSQL.jjt @@ -29,6 +29,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** * Add support for MERGE (INTO) statement * Add support Error Logging for INSERT, UPDATE, DELETE + * Add support for exception handlers in compound triggers, + * Allow multiple exception handlers. Parse DeclarativeSection + * correctly in compound triggers. * * Andreas Dangel 06/2024 *==================================================================== @@ -462,13 +465,13 @@ ASTBlock Block() : { // Included as part of statement() [ - - DeclarativeSection() + + [ DeclarativeSection() ] ] (Statement())* - (ExceptionHandler())? + [ (ExceptionHandler())+] [ ] // Optional END Identifier has to match the label { return jjtThis ; } } @@ -497,7 +500,7 @@ ASTPackageSpecification PackageSpecification() : ( ( | ) - DeclarativeSection() + [ DeclarativeSection() ] [ID()] ";" ) @@ -523,10 +526,10 @@ ASTPackageBody PackageBody() : ( ( | ) - DeclarativeSection() //SRT 20110524 Allow PLDOc in Type Bodies + [ DeclarativeSection() ] //SRT 20110524 Allow PLDOc in Type Bodies - [ (Statement())* (ExceptionHandler())? ] [ID()] ";" + [ (Statement())* [ (ExceptionHandler())+] ] [ID()] ";" ) ) @@ -534,36 +537,40 @@ ASTPackageBody PackageBody() : { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } } +/** + * https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/block.html#GUID-9ACEB9ED-567E-4E1A-A16A-B8B35214FC9D__CJAIABJJ + */ ASTDeclarativeUnit DeclarativeUnit() : {} { ( - Pragma() | - LOOKAHEAD(2) - ExceptionDeclaration() | - LOOKAHEAD((|) QualifiedID() ( | ) ) //SRT 20110616 - make sue soen't break object type - SubTypeDefinition() | - LOOKAHEAD((|) QualifiedID() ) //SRT 20111117 - Special case of parameterless methods:choose method in preference to variable - ProgramUnit() | - LOOKAHEAD(4) - VariableOrConstantDeclaration() | - CursorSpecification() | - CollectionDeclaration() | - //ProgramUnit() - //|TypeMethod() - MethodDeclaration() - |CompilationDeclarationFragment() + Pragma() | + LOOKAHEAD(2) ExceptionDeclaration() | + LOOKAHEAD((|) QualifiedID() ( | ) ) SubTypeDefinition() | + //SRT 20111117 - Special case of parameterless methods:choose method in preference to variable + LOOKAHEAD((|) QualifiedID() ) ProgramUnit() | + LOOKAHEAD(4) VariableOrConstantDeclaration() | + CursorSpecification() | + CollectionDeclaration() | + //ProgramUnit()| + //TypeMethod()| + MethodDeclaration() | + CompilationDeclarationFragment() ) - { return jjtThis ; } + { return jjtThis; } } -ASTDeclarativeSection DeclarativeSection() : +/** + * "Declare Section" + * + * https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/block.html#GUID-9ACEB9ED-567E-4E1A-A16A-B8B35214FC9D__CJAIABJJ + */ +ASTDeclarativeSection DeclarativeSection() : {} { - ( - DeclarativeUnit() - )* - { return jjtThis ; } + DeclarativeUnit() + ( LOOKAHEAD(||||||ID() (||Datatype()), {getToken(1).kind != BEGIN}) DeclarativeUnit() )* + { return jjtThis; } } ASTCompilationDeclarationFragment CompilationDeclarationFragment() : @@ -646,10 +653,10 @@ ASTProgramUnit ProgramUnit() : CallSpecTail() //{ System.err.println("Found CallSpecTail") ; } | ( - DeclarativeSection() + [ DeclarativeSection() ] [Pragma()] // See PMD Bug #1527 - (Statement())* (ExceptionHandler())? [ID()] + (Statement())* [ (ExceptionHandler())+] [ID()] ) ) ] @@ -1065,15 +1072,19 @@ ASTDateTimeLiteral DateTimeLiteral() : } } +/** + * https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/exception-handler.html#GUID-3FECF29B-A240-4191-A635-92C612D00C4D__CJAEIGAB + */ ASTExceptionHandler ExceptionHandler() : {} { - ( - - ( LOOKAHEAD(2) QualifiedName() ( QualifiedName())* (Statement())+ )* - [ (Statement())+ ] - ) - { return jjtThis ; } + + ( + | + QualifiedName() ( QualifiedName())* + ) + (Statement())+ + { return jjtThis; } } void Skip2NextTerminator(String initiator,String terminator) : @@ -2676,7 +2687,7 @@ ASTDeleteStatement DeleteStatement() : {} { [ ] - ( TableReference() | "(" TableReference() ")" ) + ( LOOKAHEAD(2) "(" TableReference() ")" | TableReference() ) [ WhereClause() ] [ ReturningClause() ] [ ErrorLoggingClause() ] @@ -2922,6 +2933,9 @@ ASTConditionalCompilationStatement ConditionalCompilationStatement() : { return jjtThis ; } } +/** + * https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/block.html#GUID-9ACEB9ED-567E-4E1A-A16A-B8B35214FC9D__CJACIHEC + */ ASTSubTypeDefinition SubTypeDefinition() : { Token start, subtype_name=null, constraint=null, base_type=null; @@ -4160,8 +4174,8 @@ ASTTypeMethod TypeMethod() : [ ";" ] // This only exists in the type body | // SRT 20110524 Not really a Declaration any more ... ( - DeclarativeSection() - (Statement())* (ExceptionHandler())? [ID()] + [ DeclarativeSection() ] + (Statement())* [ (ExceptionHandler())+] [ID()] ";" // This only exists in the type body ) ) @@ -4433,9 +4447,9 @@ ASTTypeBody TypeBody() : ( ( | ) - DeclarativeSection() //SRT 20110524 Allow PLDOc in Type Bodies + [ DeclarativeSection() ] //SRT 20110524 Allow PLDOc in Type Bodies - [ (Statement())* (ExceptionHandler())? ] [ID()] ";" + [ (Statement())* [ (ExceptionHandler())+] ] [ID()] ";" ) ) } @@ -4592,7 +4606,7 @@ ASTTriggerUnit TriggerUnit() : // referencing_clause - there may be ZERO subclauses - [ (( | | ) ID())*] + [LOOKAHEAD({isKeyword("REFERENCING")}) KEYWORD("REFERENCING") (( | | ) ID())*] [] // end of simple_dml_trigger (incorporating compound_dml_trigger ) @@ -4617,20 +4631,24 @@ ASTTriggerUnit TriggerUnit() : { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } } - +/** + * https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/CREATE-TRIGGER-statement.html#GUID-AF9E33F1-64D1-4382-A6A4-EC33C36F237B__GUID-2CD49225-7507-458B-8BDF-21C56AFC3527 + * + * Note: The DeclarativeSection (declare_section) before BEGIN is not documented, but might be valid. See #4270 + */ ASTTriggerTimingPointSection TriggerTimingPointSection() : { StringBuilder sb = new StringBuilder(); } { - ( ( | | ) { sb.append(token.getImage()) ; } ( | ) {sb.append(" "); sb.append(token.getImage()) ; } + [ DeclarativeSection() ] (Statement())+ + [ (ExceptionHandler())+ ] ( | | ) ( | ) ";" - ) { //Add a TRIGGER ENTRY for each timing point section } @@ -4638,36 +4656,19 @@ ASTTriggerTimingPointSection TriggerTimingPointSection() : } - +/** + * https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/CREATE-TRIGGER-statement.html#GUID-AF9E33F1-64D1-4382-A6A4-EC33C36F237B__CJACFCDJ +*/ ASTCompoundTriggerBlock CompoundTriggerBlock() : +{} { -} -{ - + - ( - //Problems making the declaration optional - //DeclarativeSection() - //(TriggerTimingPointSection())+ - ( - TriggerTimingPointSection()| - Pragma() | - LOOKAHEAD(2) - ExceptionDeclaration() | - LOOKAHEAD(2) - SubTypeDefinition() | - LOOKAHEAD(4) - VariableOrConstantDeclaration() | - CursorSpecification() | - CollectionDeclaration() | - ProgramUnit() - )* + [ DeclarativeSection() ] + (TriggerTimingPointSection())+ - - ) - - [ID()] ";" - { return jjtThis ; } + [ID()] ";" + { return jjtThis; } } @@ -5150,7 +5151,6 @@ TOKEN [IGNORE_CASE]: | | | - | | | | @@ -5200,7 +5200,6 @@ TOKEN [IGNORE_CASE]: | | | - | | | | @@ -5408,9 +5407,10 @@ TOKEN : * PL/SQL Reserved words * * https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-reserved-words-keywords.html + * https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/plsql-language-fundamentals.html#GUID-53E09662-5AD4-4530-8C6B-FF3F7C7430D5 * * Note: This production is not used, it is just here for reference of collecting all reserved words. - * Reserved words cannot be used a identifiers. + * Reserved words _cannot_ be used a identifiers. */ void RESERVED_WORD() #void: {} { @@ -5515,6 +5515,11 @@ void KEYWORD(String id) #void: } +/** + * PL/SQL Keywords. They can be used as ordinary identifiers, but it is not recommended. + * + * https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/plsql-language-fundamentals.html#GUID-53E09662-5AD4-4530-8C6B-FF3F7C7430D5 + */ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} { // PL/SQL UNRESERVED KEYWORDS - V$RESERVED.RESERVED='N' @@ -5550,7 +5555,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| //| -//| test_unreserved_keyword.pks +| | | | @@ -5570,7 +5575,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| | -//| //| test_unreserved_keyword.pks //| | //-test_unreserved_keyword.pks @@ -5591,7 +5595,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| //| -//| +| | //| //| @@ -5612,7 +5616,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| | -//| | //| //| @@ -5684,7 +5687,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| | -| //| //| //| @@ -5704,7 +5706,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| | -//| //| //| | @@ -5773,7 +5774,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| //| -//| +| //| //| //| @@ -5848,7 +5849,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| //| -//| //| //| //| @@ -5856,7 +5856,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| //| -//| +| //| //| //| @@ -5896,7 +5896,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| //| -//| +| //| //| //| @@ -6150,13 +6150,13 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| //| -//| +| | //| //| //| | -//| +| //| //| //| @@ -6235,7 +6235,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| | -//| +| //| //| //| @@ -6281,7 +6281,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| | -//| //| | //| @@ -6393,7 +6392,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| //| -//| //| //| //| @@ -6410,7 +6408,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| //| -//| +| //| | //| @@ -6439,7 +6437,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| //| -//| //| //| //| @@ -6604,18 +6601,15 @@ ASTID ID(): {} | //SYNTAX //RESERVED WORD | //SYNTAX //RESERVED WORD | //RESERVED WORD - | | //RESERVED WORD //20120429 | | //| // | | - | //SYNTAX | //201020430 | //| //RESERVED WORD //201020430 | - | //SYNTAX | //RESERVED WPRDS | | //RESERVED WPRDS @@ -6629,7 +6623,6 @@ ASTID ID(): {} //20120501 | | | //RESERVED WORD - | //SYNTAX | //RESERVED WORD | //RESERVED WORD | //RESERVED WORD @@ -6641,7 +6634,6 @@ ASTID ID(): {} //| | //| //SYNTAX | //SYNTAX //RESERVED WORD - | //SYNTAX //| //SYNTAX //RESERVED WORD //| //SYNTAX //20120501 | @@ -6654,7 +6646,6 @@ ASTID ID(): {} | //SYNTAX | //RESERVED WORD //20120501 | - | //SYNTAX //20120501 | | //RESERVED WORD //20120501 | @@ -6691,7 +6682,7 @@ ASTID ID(): {} | //20120501 | - | |