[plsql] Support Error Logging in INSERT, UPDATE, DELETE

Fixes #2779
This commit is contained in:
Andreas Dangel 2024-06-07 12:16:43 +02:00
parent 28c6c8e121
commit d47ca10029
No known key found for this signature in database
GPG Key ID: 93450DF2DF9A3FA3
5 changed files with 202 additions and 4 deletions

View File

@ -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

View File

@ -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() :
{}
{
<DELETE> [ <FROM> ]
( TableReference() | <ONLY> "(" TableReference() ")" )
[ WhereClause() ]
[ ReturningClause() ]
[ ErrorLoggingClause() ]
{ return jjtThis; }
}

View File

@ -41,4 +41,9 @@ class PlsqlTreeDumpTest extends BaseTreeDumpTest {
void parseSelectIntoAssociativeArrayType() {
doTest("SelectIntoArray");
}
@Test
void errorLoggingClause() {
doTest("ErrorLoggingClause2779");
}
}

View File

@ -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;
/

View File

@ -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"]