[plsql] Support MERGE statement

Fixes #1934
This commit is contained in:
Andreas Dangel 2024-06-07 11:20:50 +02:00
parent 28c6c8e121
commit 72bf5d07b4
No known key found for this signature in database
GPG Key ID: 93450DF2DF9A3FA3
5 changed files with 220 additions and 19 deletions

View File

@ -15,6 +15,8 @@ This is a {{ site.pmd.release_type }} release.
### 🚀 New and noteworthy
### 🐛 Fixed Issues
* plsql
* [#1934](https://github.com/pmd/pmd/issues/1934): \[plsql] ParseException with MERGE statement in anonymous block
### 🚨 API Changes

View File

@ -27,6 +27,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Add support for MERGE (INTO) statement
*
* Andreas Dangel 06/2024
*====================================================================
* Add support for Select statement within OPEN FOR Statements
*
* Andreas Dangel 09/2021
@ -293,7 +297,8 @@ ASTInput Input() :
| DeleteStatement() [";"]
| InsertStatement() [";"]
| SelectStatement() [";"]
|(<COMMIT>|<ROLLBACK>|<SAVEPOINT>|<LOCK><TABLE>|<MERGE>|<WITH>) ReadPastNextOccurrence(";") //Ignore SQL statements in scripts
| MergeStatement() [";"]
|(<COMMIT>|<ROLLBACK>|<SAVEPOINT>|<LOCK><TABLE>|<WITH>) ReadPastNextOccurrence(";") //Ignore SQL statements in scripts
)
("/")*
)*
@ -1078,19 +1083,32 @@ void Skip2NextTerminator(String initiator,String terminator) :
if(t.getImage().equals(initiator)) count++;
while (count > 0 || !t.getImage().equals(terminator))
{
t = getNextToken();
t = getToken(1);
if(t.getImage().equals(initiator)) count++;
if(t.getImage().equals(terminator)) count--;
if((null != t.specialToken && beginToken.kind != SELECT && beginToken.kind != INSERT && beginToken.kind != UPDATE && beginToken.kind != DELETE
&& beginToken.kind != MERGE && beginToken.kind != EXECUTE && beginToken.kind != WITH) || t.kind == EOF)
return;
if (t.specialToken != null && "/".equals(t.getImage()))
return;
t = getNextToken();
t = getToken(1);
if (t.getImage().equals(initiator)) {
count++;
}
if (t.getImage().equals(terminator)) {
count--;
}
if ((null != t.specialToken
&& beginToken.kind != SELECT
&& beginToken.kind != INSERT
&& beginToken.kind != UPDATE
&& beginToken.kind != DELETE
&& beginToken.kind != MERGE
&& beginToken.kind != EXECUTE
&& beginToken.kind != WITH)
|| t.kind == EOF) {
return;
}
if (t.specialToken != null && "/".equals(t.getImage())) {
return;
}
}
}
{
{ return; }
{ return; }
}
/*
@ -1256,10 +1274,10 @@ ASTSqlStatement SqlStatement(String initiator, String terminator) :
|<LOCK><TABLE>{jjtThis.setType(ASTSqlStatement.Type.LOCK_TABLE); }
|<MERGE>{jjtThis.setType(ASTSqlStatement.Type.MERGE); }
|<WITH>)
Skip2NextTerminator(initiator, terminator)
{
return jjtThis ;
}
Skip2NextTerminator(initiator, terminator)
{
return jjtThis;
}
}
void AbstractSelectStatement(AbstractSelectStatement node) #void :
@ -2346,6 +2364,7 @@ ASTUnlabelledStatement UnlabelledStatement() :
UpdateStatement() ";" |
DeleteStatement() ";" |
InsertStatement() ";" |
LOOKAHEAD(2) MergeStatement() ";" |
LOOKAHEAD(["("] <SELECT>|<UPDATE>|<INSERT>|<DELETE>|<COMMIT>|<ROLLBACK>|<SAVEPOINT>|<SET><TRANSACTION>|<LOCK><TABLE>|<MERGE>|<WITH>) SqlStatement(null,";") [";"]
| LOOKAHEAD(3) ContinueStatement() ";" // CONTINUE keyword was added in 11G, so Oracle compilation supports CONTINUE as a variable name
| CaseStatement() ";"
@ -2654,6 +2673,55 @@ ASTDeleteStatement DeleteStatement() :
{ return jjtThis; }
}
/**
* https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/MERGE.html#GUID-5692CCB7-24D9-4C0E-81A7-A22436DC968F
* https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/MERGE.html#GUID-5692CCB7-24D9-4C0E-81A7-A22436DC968F
*/
ASTMergeStatement MergeStatement() :
{}
{
<MERGE> <INTO> [ LOOKAHEAD(2) SchemaName() "." ] TableName() [ TableAlias() ]
<USING>
(
LOOKAHEAD(3) "(" ValuesClause() ")"
| "(" Subquery() ")" [ TableAlias() ]
| [ LOOKAHEAD(2) SchemaName() "." ] TableName() [ TableAlias() ]
)
<ON> "(" Condition() ")"
[ LOOKAHEAD(MergeUpdateClausePrefix()) MergeUpdateClause() ]
[ LOOKAHEAD(2) MergeInsertClause() ]
[ ErrorLoggingClause() ]
[ ReturningClause() ]
{ return jjtThis; }
}
void MergeUpdateClausePrefix() #void:
{}
{
<WHEN> KEYWORD("MATCHED")
}
ASTMergeUpdateClause MergeUpdateClause() :
{}
{
MergeUpdateClausePrefix() <THEN> <UPDATE> <SET>
[ LOOKAHEAD(2) TableName() "." ] Column() "=" ( LOOKAHEAD(2) "(" Subquery() ")" | Expression() | <_DEFAULT> )
( "," [ LOOKAHEAD(2) TableName() "." ] Column() "=" ( LOOKAHEAD(2) "(" Subquery() ")" | Expression() | <_DEFAULT> ) )*
[ WhereClause() ]
[ <DELETE> WhereClause() ]
{ return jjtThis; }
}
ASTMergeInsertClause MergeInsertClause() :
{}
{
<WHEN> <NOT> KEYWORD("MATCHED") <THEN> <INSERT>
[ "(" Column() ( "," Column() )* ")" ]
ValuesClause()
[ WhereClause() ]
{ return jjtThis; }
}
/** Scope rule: the loop index only exists within the Loop */
ASTForStatement ForStatement() :
{}
@ -5426,14 +5494,15 @@ void RESERVED_WORD() #void: {}
void KEYWORD(String id) #void:
{}
{
<IDENTIFIER>
{
if (!token.getImage().equalsIgnoreCase(id)) {
if (!isKeyword(id)) {
String eol = System.getProperty("line.separator", "\n");
throw new ParseException("Encountered \"" + token.getImage() + "\" "
+ "Was expecting: " + id).withLocation(token);
throw new ParseException("Encountered \"" + getToken(1).getImage() + "\" "
+ "Was expecting: \"" + id + "\"").withLocation(token);
}
}
<IDENTIFIER>
}
ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {}

View File

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

View File

@ -0,0 +1,17 @@
--
-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html
--
-- See https://github.com/pmd/pmd/issues/1934
BEGIN
MERGE INTO jhs_translations b
USING ( SELECT 'PROM_EDIT_PROM_NR' key1,'Edycja promocji nr' text,123123 lce_id FROM dual ) e
ON (b.key1 = e.key1 and b.lce_id=e.lce_id)
WHEN MATCHED
THEN UPDATE SET b.text = e.text
WHEN NOT MATCHED
THEN INSERT (ID,KEY1, TEXT,LCE_ID) values (JHS_SEQ.NEXTVAL,'PROM_EDIT_PROM_NR','Edycja promocji nr',123123);
END;
/

View File

@ -0,0 +1,108 @@
+- Input[@CanonicalImage = null, @ExcludedLinesCount = 0, @ExcludedRangesCount = 0]
+- Global[@CanonicalImage = null]
+- Block[@CanonicalImage = null]
+- Statement[@CanonicalImage = null]
+- UnlabelledStatement[@CanonicalImage = null]
+- MergeStatement[@CanonicalImage = null]
+- TableName[@CanonicalImage = "JHS_TRANSLATIONS", @Image = "jhs_translations"]
| +- ID[@CanonicalImage = "JHS_TRANSLATIONS", @Image = "jhs_translations"]
+- TableAlias[@CanonicalImage = "B", @Image = "b"]
| +- ID[@CanonicalImage = "B", @Image = "b"]
+- QueryBlock[@All = false, @CanonicalImage = null, @Distinct = false, @Unique = false]
| +- SelectList[@CanonicalImage = null]
| | +- SqlExpression[@CanonicalImage = "\'PROM_EDIT_PROM_NR\'", @Image = "\'PROM_EDIT_PROM_NR\'"]
| | | +- PrimaryPrefix[@CanonicalImage = "\'PROM_EDIT_PROM_NR\'", @Image = "\'PROM_EDIT_PROM_NR\'", @SelfModifier = false]
| | | +- Literal[@CanonicalImage = "\'PROM_EDIT_PROM_NR\'", @Image = "\'PROM_EDIT_PROM_NR\'"]
| | | +- StringLiteral[@CanonicalImage = "\'PROM_EDIT_PROM_NR\'", @Image = "\'PROM_EDIT_PROM_NR\'", @String = "PROM_EDIT_PROM_NR"]
| | +- ColumnAlias[@CanonicalImage = "KEY1", @Image = "key1"]
| | | +- ID[@CanonicalImage = "KEY1", @Image = "key1"]
| | +- SqlExpression[@CanonicalImage = "\'EDYCJA PROMOCJI NR\'", @Image = "\'Edycja promocji nr\'"]
| | | +- PrimaryPrefix[@CanonicalImage = "\'EDYCJA PROMOCJI NR\'", @Image = "\'Edycja promocji nr\'", @SelfModifier = false]
| | | +- Literal[@CanonicalImage = "\'EDYCJA PROMOCJI NR\'", @Image = "\'Edycja promocji nr\'"]
| | | +- StringLiteral[@CanonicalImage = "\'EDYCJA PROMOCJI NR\'", @Image = "\'Edycja promocji nr\'", @String = "Edycja promocji nr"]
| | +- ColumnAlias[@CanonicalImage = "TEXT", @Image = "text"]
| | | +- ID[@CanonicalImage = "TEXT", @Image = "text"]
| | +- SqlExpression[@CanonicalImage = "123123", @Image = "123123"]
| | | +- PrimaryPrefix[@CanonicalImage = "123123", @Image = "123123", @SelfModifier = false]
| | | +- Literal[@CanonicalImage = "123123", @Image = "123123"]
| | | +- NumericLiteral[@CanonicalImage = "123123", @Image = "123123"]
| | +- ColumnAlias[@CanonicalImage = "LCE_ID", @Image = "lce_id"]
| | +- ID[@CanonicalImage = "LCE_ID", @Image = "lce_id"]
| +- FromClause[@CanonicalImage = null]
| +- TableReference[@CanonicalImage = null]
| +- TableName[@CanonicalImage = "DUAL", @Image = "dual"]
| +- ID[@CanonicalImage = "DUAL", @Image = "dual"]
+- TableAlias[@CanonicalImage = "E", @Image = "e"]
| +- ID[@CanonicalImage = "E", @Image = "e"]
+- Condition[@CanonicalImage = null]
| +- CompoundCondition[@CanonicalImage = null, @Type = "AND"]
| +- ComparisonCondition[@CanonicalImage = null, @Operator = "="]
| | +- SqlExpression[@CanonicalImage = "B.KEY1", @Image = "b.key1"]
| | | +- PrimaryPrefix[@CanonicalImage = "B.KEY1", @Image = "b.key1", @SelfModifier = false]
| | | +- SimpleExpression[@CanonicalImage = "B.KEY1", @Image = "b.key1"]
| | | +- TableName[@CanonicalImage = "B", @Image = "b"]
| | | | +- ID[@CanonicalImage = "B", @Image = "b"]
| | | +- Column[@CanonicalImage = "KEY1", @Image = "key1"]
| | | +- ID[@CanonicalImage = "KEY1", @Image = "key1"]
| | +- SqlExpression[@CanonicalImage = "E.KEY1", @Image = "e.key1"]
| | +- PrimaryPrefix[@CanonicalImage = "E.KEY1", @Image = "e.key1", @SelfModifier = false]
| | +- SimpleExpression[@CanonicalImage = "E.KEY1", @Image = "e.key1"]
| | +- TableName[@CanonicalImage = "E", @Image = "e"]
| | | +- ID[@CanonicalImage = "E", @Image = "e"]
| | +- Column[@CanonicalImage = "KEY1", @Image = "key1"]
| | +- ID[@CanonicalImage = "KEY1", @Image = "key1"]
| +- Condition[@CanonicalImage = null]
| +- CompoundCondition[@CanonicalImage = null, @Type = null]
| +- ComparisonCondition[@CanonicalImage = null, @Operator = "="]
| +- SqlExpression[@CanonicalImage = "B.LCE_ID", @Image = "b.lce_id"]
| | +- PrimaryPrefix[@CanonicalImage = "B.LCE_ID", @Image = "b.lce_id", @SelfModifier = false]
| | +- SimpleExpression[@CanonicalImage = "B.LCE_ID", @Image = "b.lce_id"]
| | +- TableName[@CanonicalImage = "B", @Image = "b"]
| | | +- ID[@CanonicalImage = "B", @Image = "b"]
| | +- Column[@CanonicalImage = "LCE_ID", @Image = "lce_id"]
| | +- ID[@CanonicalImage = "LCE_ID", @Image = "lce_id"]
| +- SqlExpression[@CanonicalImage = "E.LCE_ID", @Image = "e.lce_id"]
| +- PrimaryPrefix[@CanonicalImage = "E.LCE_ID", @Image = "e.lce_id", @SelfModifier = false]
| +- SimpleExpression[@CanonicalImage = "E.LCE_ID", @Image = "e.lce_id"]
| +- TableName[@CanonicalImage = "E", @Image = "e"]
| | +- ID[@CanonicalImage = "E", @Image = "e"]
| +- Column[@CanonicalImage = "LCE_ID", @Image = "lce_id"]
| +- ID[@CanonicalImage = "LCE_ID", @Image = "lce_id"]
+- MergeUpdateClause[@CanonicalImage = null]
| +- TableName[@CanonicalImage = "B", @Image = "b"]
| | +- ID[@CanonicalImage = "B", @Image = "b"]
| +- Column[@CanonicalImage = "TEXT", @Image = "text"]
| | +- ID[@CanonicalImage = "TEXT", @Image = "text"]
| +- Expression[@CanonicalImage = "E.TEXT", @Image = "e.text"]
| +- PrimaryPrefix[@CanonicalImage = "E.TEXT", @Image = "e.text", @SelfModifier = false]
| +- SimpleExpression[@CanonicalImage = "E.TEXT", @Image = "e.text"]
| +- TableName[@CanonicalImage = "E", @Image = "e"]
| | +- ID[@CanonicalImage = "E", @Image = "e"]
| +- Column[@CanonicalImage = "TEXT", @Image = "text"]
| +- ID[@CanonicalImage = "TEXT", @Image = "text"]
+- MergeInsertClause[@CanonicalImage = null]
+- Column[@CanonicalImage = "ID", @Image = "ID"]
| +- ID[@CanonicalImage = "ID", @Image = "ID"]
+- Column[@CanonicalImage = "KEY1", @Image = "KEY1"]
| +- ID[@CanonicalImage = "KEY1", @Image = "KEY1"]
+- Column[@CanonicalImage = "TEXT", @Image = "TEXT"]
| +- ID[@CanonicalImage = "TEXT", @Image = "TEXT"]
+- Column[@CanonicalImage = "LCE_ID", @Image = "LCE_ID"]
| +- ID[@CanonicalImage = "LCE_ID", @Image = "LCE_ID"]
+- ValuesClause[@CanonicalImage = null]
+- Expression[@CanonicalImage = "", @Image = ""]
| +- PrimaryPrefix[@CanonicalImage = "", @Image = "", @SelfModifier = false]
| +- SimpleExpression[@CanonicalImage = "", @Image = ""]
| +- ID[@CanonicalImage = "JHS_SEQ", @Image = "JHS_SEQ"]
+- Expression[@CanonicalImage = "\'PROM_EDIT_PROM_NR\'", @Image = "\'PROM_EDIT_PROM_NR\'"]
| +- PrimaryPrefix[@CanonicalImage = "\'PROM_EDIT_PROM_NR\'", @Image = "\'PROM_EDIT_PROM_NR\'", @SelfModifier = false]
| +- Literal[@CanonicalImage = "\'PROM_EDIT_PROM_NR\'", @Image = "\'PROM_EDIT_PROM_NR\'"]
| +- StringLiteral[@CanonicalImage = "\'PROM_EDIT_PROM_NR\'", @Image = "\'PROM_EDIT_PROM_NR\'", @String = "PROM_EDIT_PROM_NR"]
+- Expression[@CanonicalImage = "\'EDYCJA PROMOCJI NR\'", @Image = "\'Edycja promocji nr\'"]
| +- PrimaryPrefix[@CanonicalImage = "\'EDYCJA PROMOCJI NR\'", @Image = "\'Edycja promocji nr\'", @SelfModifier = false]
| +- Literal[@CanonicalImage = "\'EDYCJA PROMOCJI NR\'", @Image = "\'Edycja promocji nr\'"]
| +- StringLiteral[@CanonicalImage = "\'EDYCJA PROMOCJI NR\'", @Image = "\'Edycja promocji nr\'", @String = "Edycja promocji nr"]
+- Expression[@CanonicalImage = "123123", @Image = "123123"]
+- PrimaryPrefix[@CanonicalImage = "123123", @Image = "123123", @SelfModifier = false]
+- Literal[@CanonicalImage = "123123", @Image = "123123"]
+- NumericLiteral[@CanonicalImage = "123123", @Image = "123123"]