From d47ca10029a2b851a10aebddbb652d78c5644fe8 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 7 Jun 2024 12:16:43 +0200 Subject: [PATCH] [plsql] Support Error Logging in INSERT, UPDATE, DELETE Fixes #2779 --- docs/pages/release_notes.md | 2 + pmd-plsql/etc/grammar/PLSQL.jjt | 21 ++- .../pmd/lang/plsql/ast/PlsqlTreeDumpTest.java | 5 + .../lang/plsql/ast/ErrorLoggingClause2779.pls | 29 ++++ .../lang/plsql/ast/ErrorLoggingClause2779.txt | 149 ++++++++++++++++++ 5 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ErrorLoggingClause2779.pls create mode 100644 pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ErrorLoggingClause2779.txt diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index e8d7cdbf90..9ede3ce465 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -15,6 +15,8 @@ This is a {{ site.pmd.release_type }} release. ### 🚀 New and noteworthy ### 🐛 Fixed Issues +* plsql + * [#2779](https://github.com/pmd/pmd/issues/2779): \[plsql] Error while parsing statement with (Oracle) DML Error Logging ### 🚨 API Changes diff --git a/pmd-plsql/etc/grammar/PLSQL.jjt b/pmd-plsql/etc/grammar/PLSQL.jjt index 5108997fac..25a1033d2f 100644 --- a/pmd-plsql/etc/grammar/PLSQL.jjt +++ b/pmd-plsql/etc/grammar/PLSQL.jjt @@ -27,6 +27,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** + * Add support Error Logging for INSERT, UPDATE, DELETE + * + * Andreas Dangel 06/2024 + *==================================================================== * Add support for Select statement within OPEN FOR Statements * * Andreas Dangel 09/2021 @@ -2087,7 +2091,7 @@ ASTTableReference TableReference() : { QueryTableExpression() - [ LOOKAHEAD(2) TableAlias() ] + [ LOOKAHEAD(2, {!getToken(1).getImage().equalsIgnoreCase("LOG")}) TableAlias() ] { return jjtThis; } } @@ -2457,7 +2461,7 @@ ASTCursorForLoopStatement CursorForLoopStatement() : } /** - * See https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/INSERT.html#GUID-903F8043-0254-4EE9-ACC1-CB8AC0AF3423 + * https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/INSERT.html#GUID-903F8043-0254-4EE9-ACC1-CB8AC0AF3423 */ ASTInsertStatement InsertStatement() : {} @@ -2470,6 +2474,7 @@ ASTSingleTableInsert SingleTableInsert() : {} { InsertIntoClause() ( ValuesClause() [ ReturningClause() ] | Subquery() ) + [ ErrorLoggingClause() ] { return jjtThis; } } @@ -2622,7 +2627,8 @@ ASTUpdateSetClause UpdateSetClause() : } /** - * https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/UPDATE.html#GUID-027A462D-379D-4E35-8611-410F3AC8FDA5__I2126358 + * https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/UPDATE.html#GUID-027A462D-379D-4E35-8611-410F3AC8FDA5__I2126358 + * https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/DELETE.html#GUID-156845A5-B626-412B-9F95-8869B988ABD7__I2122564 */ ASTReturningClause ReturningClause() : {} @@ -2632,7 +2638,9 @@ ASTReturningClause ReturningClause() : } /** - * https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/UPDATE.html#GUID-027A462D-379D-4E35-8611-410F3AC8FDA5__BCEEAAGC + * https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/INSERT.html#GUID-903F8043-0254-4EE9-ACC1-CB8AC0AF3423__BGBDIGAH + * https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/UPDATE.html#GUID-027A462D-379D-4E35-8611-410F3AC8FDA5__BCEEAAGC + * https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/DELETE.html#GUID-156845A5-B626-412B-9F95-8869B988ABD7__CEGCHDJF */ ASTErrorLoggingClause ErrorLoggingClause() : {} @@ -2645,12 +2653,17 @@ ASTErrorLoggingClause ErrorLoggingClause() : } +/** + * https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/DELETE.html#GUID-156845A5-B626-412B-9F95-8869B988ABD7 + */ ASTDeleteStatement DeleteStatement() : {} { [ ] ( TableReference() | "(" TableReference() ")" ) [ WhereClause() ] + [ ReturningClause() ] + [ ErrorLoggingClause() ] { return jjtThis; } } diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/PlsqlTreeDumpTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/PlsqlTreeDumpTest.java index 438184c677..45612111c7 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/PlsqlTreeDumpTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/PlsqlTreeDumpTest.java @@ -41,4 +41,9 @@ class PlsqlTreeDumpTest extends BaseTreeDumpTest { void parseSelectIntoAssociativeArrayType() { doTest("SelectIntoArray"); } + + @Test + void errorLoggingClause() { + doTest("ErrorLoggingClause2779"); + } } diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ErrorLoggingClause2779.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ErrorLoggingClause2779.pls new file mode 100644 index 0000000000..580a396391 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ErrorLoggingClause2779.pls @@ -0,0 +1,29 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +create or replace procedure test as +begin + + -- https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/INSERT.html#GUID-903F8043-0254-4EE9-ACC1-CB8AC0AF3423__BCEGDJDJ + INSERT INTO raises + SELECT employee_id, salary*1.1 FROM employees + WHERE commission_pct > .2 + LOG ERRORS INTO errlog ('my_bad') REJECT LIMIT 10; + + -- https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/UPDATE.html#GUID-027A462D-379D-4E35-8611-410F3AC8FDA5__I2135485 + UPDATE people_demo1 p SET VALUE(p) = + (SELECT VALUE(q) FROM people_demo2 q + WHERE p.department_id = q.department_id) + WHERE p.department_id = 10 + LOG ERRORS INTO errlog ('my_bad') REJECT LIMIT 10; + + -- https://github.com/pmd/pmd/issues/2779 + delete from test_table talias + log errors into err$_test_table reject limit unlimited; + + -- without a table alias + delete from test_table + log errors into err$_test_table reject limit unlimited; +end; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ErrorLoggingClause2779.txt b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ErrorLoggingClause2779.txt new file mode 100644 index 0000000000..ce2cf4c5e2 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ErrorLoggingClause2779.txt @@ -0,0 +1,149 @@ ++- Input[@CanonicalImage = null, @ExcludedLinesCount = 0, @ExcludedRangesCount = 0] + +- Global[@CanonicalImage = null] + +- ProgramUnit[@CanonicalImage = null, @MethodName = "test", @Name = "test", @ObjectName = null] + +- MethodDeclarator[@CanonicalImage = "TEST", @Image = "test", @ParameterCount = 1] + | +- ObjectNameDeclaration[@CanonicalImage = "TEST", @Image = "test"] + | +- ID[@CanonicalImage = "TEST", @Image = "test"] + +- DeclarativeSection[@CanonicalImage = null] + +- Statement[@CanonicalImage = null] + | +- UnlabelledStatement[@CanonicalImage = null] + | +- InsertStatement[@CanonicalImage = null] + | +- SingleTableInsert[@CanonicalImage = null] + | +- InsertIntoClause[@CanonicalImage = null] + | | +- DMLTableExpressionClause[@CanonicalImage = null] + | | +- TableName[@CanonicalImage = "RAISES", @Image = "raises"] + | | +- ID[@CanonicalImage = "RAISES", @Image = "raises"] + | +- QueryBlock[@All = false, @CanonicalImage = null, @Distinct = false, @Unique = false] + | | +- SelectList[@CanonicalImage = null] + | | | +- SqlExpression[@CanonicalImage = "EMPLOYEE_ID", @Image = "employee_id"] + | | | | +- PrimaryPrefix[@CanonicalImage = "EMPLOYEE_ID", @Image = "employee_id", @SelfModifier = false] + | | | | +- SimpleExpression[@CanonicalImage = "EMPLOYEE_ID", @Image = "employee_id"] + | | | | +- Column[@CanonicalImage = "EMPLOYEE_ID", @Image = "employee_id"] + | | | | +- ID[@CanonicalImage = "EMPLOYEE_ID", @Image = "employee_id"] + | | | +- SqlExpression[@CanonicalImage = "SALARY * 1.1", @Image = "salary * 1.1"] + | | | +- MultiplicativeExpression[@CanonicalImage = "SALARY * 1.1", @Image = "salary * 1.1"] + | | | +- PrimaryPrefix[@CanonicalImage = "SALARY", @Image = "salary", @SelfModifier = false] + | | | | +- SimpleExpression[@CanonicalImage = "SALARY", @Image = "salary"] + | | | | +- Column[@CanonicalImage = "SALARY", @Image = "salary"] + | | | | +- ID[@CanonicalImage = "SALARY", @Image = "salary"] + | | | +- PrimaryPrefix[@CanonicalImage = "1.1", @Image = "1.1", @SelfModifier = false] + | | | +- Literal[@CanonicalImage = "1.1", @Image = "1.1"] + | | | +- NumericLiteral[@CanonicalImage = "1.1", @Image = "1.1"] + | | +- FromClause[@CanonicalImage = null] + | | | +- TableReference[@CanonicalImage = null] + | | | +- TableName[@CanonicalImage = "EMPLOYEES", @Image = "employees"] + | | | +- ID[@CanonicalImage = "EMPLOYEES", @Image = "employees"] + | | +- WhereClause[@CanonicalImage = null] + | | +- Condition[@CanonicalImage = null] + | | +- CompoundCondition[@CanonicalImage = null, @Type = null] + | | +- ComparisonCondition[@CanonicalImage = null, @Operator = ">"] + | | +- SqlExpression[@CanonicalImage = "COMMISSION_PCT", @Image = "commission_pct"] + | | | +- PrimaryPrefix[@CanonicalImage = "COMMISSION_PCT", @Image = "commission_pct", @SelfModifier = false] + | | | +- SimpleExpression[@CanonicalImage = "COMMISSION_PCT", @Image = "commission_pct"] + | | | +- Column[@CanonicalImage = "COMMISSION_PCT", @Image = "commission_pct"] + | | | +- ID[@CanonicalImage = "COMMISSION_PCT", @Image = "commission_pct"] + | | +- SqlExpression[@CanonicalImage = ".2", @Image = ".2"] + | | +- PrimaryPrefix[@CanonicalImage = ".2", @Image = ".2", @SelfModifier = false] + | | +- Literal[@CanonicalImage = ".2", @Image = ".2"] + | | +- NumericLiteral[@CanonicalImage = ".2", @Image = ".2"] + | +- ErrorLoggingClause[@CanonicalImage = null] + | +- TableName[@CanonicalImage = "ERRLOG", @Image = "errlog"] + | | +- ID[@CanonicalImage = "ERRLOG", @Image = "errlog"] + | +- SqlExpression[@CanonicalImage = "\'MY_BAD\'", @Image = "\'my_bad\'"] + | | +- PrimaryPrefix[@CanonicalImage = "\'MY_BAD\'", @Image = "\'my_bad\'", @SelfModifier = false] + | | +- Literal[@CanonicalImage = "\'MY_BAD\'", @Image = "\'my_bad\'"] + | | +- StringLiteral[@CanonicalImage = "\'MY_BAD\'", @Image = "\'my_bad\'", @String = "my_bad"] + | +- NumericLiteral[@CanonicalImage = "10", @Image = "10"] + +- Statement[@CanonicalImage = null] + | +- UnlabelledStatement[@CanonicalImage = null] + | +- UpdateStatement[@CanonicalImage = null] + | +- DMLTableExpressionClause[@CanonicalImage = null] + | | +- TableName[@CanonicalImage = "PEOPLE_DEMO1", @Image = "people_demo1"] + | | +- ID[@CanonicalImage = "PEOPLE_DEMO1", @Image = "people_demo1"] + | +- TableAlias[@CanonicalImage = "P", @Image = "p"] + | | +- ID[@CanonicalImage = "P", @Image = "p"] + | +- UpdateSetClause[@CanonicalImage = null] + | | +- TableAlias[@CanonicalImage = "P", @Image = "p"] + | | | +- ID[@CanonicalImage = "P", @Image = "p"] + | | +- QueryBlock[@All = false, @CanonicalImage = null, @Distinct = false, @Unique = false] + | | +- SelectList[@CanonicalImage = null] + | | | +- SqlExpression[@CanonicalImage = "VALUE", @Image = "VALUE"] + | | | +- PrimaryPrefix[@CanonicalImage = "VALUE", @Image = "VALUE", @SelfModifier = false] + | | | +- FunctionCall[@CanonicalImage = "VALUE", @Image = "VALUE"] + | | | +- FunctionName[@CanonicalImage = "VALUE", @Image = "VALUE"] + | | | | +- ID[@CanonicalImage = "VALUE", @Image = "VALUE"] + | | | +- Arguments[@ArgumentCount = 1, @CanonicalImage = null] + | | | +- ArgumentList[@CanonicalImage = null] + | | | +- Argument[@CanonicalImage = null] + | | | +- Expression[@CanonicalImage = "Q", @Image = "q"] + | | | +- PrimaryPrefix[@CanonicalImage = "Q", @Image = "q", @SelfModifier = false] + | | | +- SimpleExpression[@CanonicalImage = "Q", @Image = "q"] + | | | +- Column[@CanonicalImage = "Q", @Image = "q"] + | | | +- ID[@CanonicalImage = "Q", @Image = "q"] + | | +- FromClause[@CanonicalImage = null] + | | | +- TableReference[@CanonicalImage = null] + | | | +- TableName[@CanonicalImage = "PEOPLE_DEMO2", @Image = "people_demo2"] + | | | | +- ID[@CanonicalImage = "PEOPLE_DEMO2", @Image = "people_demo2"] + | | | +- TableAlias[@CanonicalImage = "Q", @Image = "q"] + | | | +- ID[@CanonicalImage = "Q", @Image = "q"] + | | +- WhereClause[@CanonicalImage = null] + | | +- Condition[@CanonicalImage = null] + | | +- CompoundCondition[@CanonicalImage = null, @Type = null] + | | +- ComparisonCondition[@CanonicalImage = null, @Operator = "="] + | | +- SqlExpression[@CanonicalImage = "P.DEPARTMENT_ID", @Image = "p.department_id"] + | | | +- PrimaryPrefix[@CanonicalImage = "P.DEPARTMENT_ID", @Image = "p.department_id", @SelfModifier = false] + | | | +- SimpleExpression[@CanonicalImage = "P.DEPARTMENT_ID", @Image = "p.department_id"] + | | | +- TableName[@CanonicalImage = "P", @Image = "p"] + | | | | +- ID[@CanonicalImage = "P", @Image = "p"] + | | | +- Column[@CanonicalImage = "DEPARTMENT_ID", @Image = "department_id"] + | | | +- ID[@CanonicalImage = "DEPARTMENT_ID", @Image = "department_id"] + | | +- SqlExpression[@CanonicalImage = "Q.DEPARTMENT_ID", @Image = "q.department_id"] + | | +- PrimaryPrefix[@CanonicalImage = "Q.DEPARTMENT_ID", @Image = "q.department_id", @SelfModifier = false] + | | +- SimpleExpression[@CanonicalImage = "Q.DEPARTMENT_ID", @Image = "q.department_id"] + | | +- TableName[@CanonicalImage = "Q", @Image = "q"] + | | | +- ID[@CanonicalImage = "Q", @Image = "q"] + | | +- Column[@CanonicalImage = "DEPARTMENT_ID", @Image = "department_id"] + | | +- ID[@CanonicalImage = "DEPARTMENT_ID", @Image = "department_id"] + | +- WhereClause[@CanonicalImage = null] + | | +- Condition[@CanonicalImage = null] + | | +- CompoundCondition[@CanonicalImage = null, @Type = null] + | | +- ComparisonCondition[@CanonicalImage = null, @Operator = "="] + | | +- SqlExpression[@CanonicalImage = "P.DEPARTMENT_ID", @Image = "p.department_id"] + | | | +- PrimaryPrefix[@CanonicalImage = "P.DEPARTMENT_ID", @Image = "p.department_id", @SelfModifier = false] + | | | +- SimpleExpression[@CanonicalImage = "P.DEPARTMENT_ID", @Image = "p.department_id"] + | | | +- TableName[@CanonicalImage = "P", @Image = "p"] + | | | | +- ID[@CanonicalImage = "P", @Image = "p"] + | | | +- Column[@CanonicalImage = "DEPARTMENT_ID", @Image = "department_id"] + | | | +- ID[@CanonicalImage = "DEPARTMENT_ID", @Image = "department_id"] + | | +- SqlExpression[@CanonicalImage = "10", @Image = "10"] + | | +- PrimaryPrefix[@CanonicalImage = "10", @Image = "10", @SelfModifier = false] + | | +- Literal[@CanonicalImage = "10", @Image = "10"] + | | +- NumericLiteral[@CanonicalImage = "10", @Image = "10"] + | +- ErrorLoggingClause[@CanonicalImage = null] + | +- TableName[@CanonicalImage = "ERRLOG", @Image = "errlog"] + | | +- ID[@CanonicalImage = "ERRLOG", @Image = "errlog"] + | +- SqlExpression[@CanonicalImage = "\'MY_BAD\'", @Image = "\'my_bad\'"] + | | +- PrimaryPrefix[@CanonicalImage = "\'MY_BAD\'", @Image = "\'my_bad\'", @SelfModifier = false] + | | +- Literal[@CanonicalImage = "\'MY_BAD\'", @Image = "\'my_bad\'"] + | | +- StringLiteral[@CanonicalImage = "\'MY_BAD\'", @Image = "\'my_bad\'", @String = "my_bad"] + | +- NumericLiteral[@CanonicalImage = "10", @Image = "10"] + +- Statement[@CanonicalImage = null] + | +- UnlabelledStatement[@CanonicalImage = null] + | +- DeleteStatement[@CanonicalImage = null] + | +- TableReference[@CanonicalImage = null] + | | +- TableName[@CanonicalImage = "TEST_TABLE", @Image = "test_table"] + | | | +- ID[@CanonicalImage = "TEST_TABLE", @Image = "test_table"] + | | +- TableAlias[@CanonicalImage = "TALIAS", @Image = "talias"] + | | +- ID[@CanonicalImage = "TALIAS", @Image = "talias"] + | +- ErrorLoggingClause[@CanonicalImage = null] + | +- TableName[@CanonicalImage = "ERR$_TEST_TABLE", @Image = "err$_test_table"] + | +- ID[@CanonicalImage = "ERR$_TEST_TABLE", @Image = "err$_test_table"] + +- Statement[@CanonicalImage = null] + +- UnlabelledStatement[@CanonicalImage = null] + +- DeleteStatement[@CanonicalImage = null] + +- TableReference[@CanonicalImage = null] + | +- TableName[@CanonicalImage = "TEST_TABLE", @Image = "test_table"] + | +- ID[@CanonicalImage = "TEST_TABLE", @Image = "test_table"] + +- ErrorLoggingClause[@CanonicalImage = null] + +- TableName[@CanonicalImage = "ERR$_TEST_TABLE", @Image = "err$_test_table"] + +- ID[@CanonicalImage = "ERR$_TEST_TABLE", @Image = "err$_test_table"]