parent
28c6c8e121
commit
72bf5d07b4
@ -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
|
||||
|
||||
|
@ -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 (): {}
|
||||
|
@ -41,4 +41,9 @@ class PlsqlTreeDumpTest extends BaseTreeDumpTest {
|
||||
void parseSelectIntoAssociativeArrayType() {
|
||||
doTest("SelectIntoArray");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMergeStatement() {
|
||||
doTest("MergeStatementIssue1934");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
/
|
@ -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"]
|
Loading…
x
Reference in New Issue
Block a user