From fcba29a3b733124119c4672fdd50df9e61b92c73 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 25 Jan 2019 15:34:06 +0100 Subject: [PATCH] [plsql] ParseException when using hierarchical query clause Fixes #1590 * Moves function calls down to primary expression so that functions can be used in a select expression * Restrict FunctionCall to built-in functions. User defined functions are parsed as primary expressions. * Parse function name of built-in function as general ID. * The Tokens _DEFAULT, ELSE and EXCEPTION are reserved words and not available for identifiers. --- pmd-plsql/etc/grammar/PldocAST.jjt | 177 +++++++++--------- .../plsql/ast/PLSQLParserVisitorAdapter.java | 5 + .../lang/plsql/rule/AbstractPLSQLRule.java | 5 + .../plsql/rule/codestyle/CodeFormatRule.java | 8 +- .../lang/plsql/ast/SelectExpressionsTest.java | 2 - .../plsql/ast/SelectHierarchicalTest.java | 24 +++ .../pmd/lang/plsql/ast/WhereClauseTest.java | 8 +- .../pmd/lang/plsql/ast/SelectExpressions.pls | 4 + .../pmd/lang/plsql/ast/SelectHierarchical.pls | 90 +++++++++ 9 files changed, 227 insertions(+), 96 deletions(-) create mode 100644 pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectHierarchicalTest.java create mode 100644 pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectHierarchical.pls diff --git a/pmd-plsql/etc/grammar/PldocAST.jjt b/pmd-plsql/etc/grammar/PldocAST.jjt index f08f27c0cc..91d39a0938 100644 --- a/pmd-plsql/etc/grammar/PldocAST.jjt +++ b/pmd-plsql/etc/grammar/PldocAST.jjt @@ -357,7 +357,7 @@ ASTPackageSpecification PackageSpecification() : } { ( - [ [ ] [ | ] ] + [ [ "REPLACE"] [ | ] ] simpleNode = ObjectNameDeclaration() ( @@ -388,7 +388,7 @@ ASTPackageBody PackageBody() : } { ( - [ [ ] [ | ] ] + [ [ "REPLACE"] [ | ] ] ( | ) simpleNode = ObjectNameDeclaration() ( @@ -490,7 +490,7 @@ ASTProgramUnit ProgramUnit() : { ( - [ [ ] [ | ] ] + [ [ "REPLACE"] [ | ] ] MethodDeclarator() @@ -684,7 +684,7 @@ ASTDatatype Datatype() : LOOKAHEAD(2) simpleNode = ScalarDataTypeName() {sb.append(simpleNode.getImage());} | ( - ( [LOOKAHEAD(2) {sb.append(token.image);} ] simpleNode = QualifiedName() {sb.append(simpleNode.getImage());} + ( [LOOKAHEAD(2) "REF" {sb.append(token.image);} ] simpleNode = QualifiedName() {sb.append(simpleNode.getImage());} //Bug 35352414 - datatype may include dblink ["@" simpleNode = QualifiedName() {sb.append("@"+simpleNode.getImage());} ] ["%" (|){sb.append("%"+token.image);} ] @@ -796,7 +796,7 @@ ASTScalarDataTypeName ScalarDataTypeName() : // reference types | //SRT Added to support pre-defined weak REF CURSOR - ( {name.append("REF CURSOR");}) | + ("REF" {name.append("REF CURSOR");}) | // object_type - defined elsewhere // scalar types - date/time: @@ -1145,6 +1145,7 @@ void RestOfStatement() #void : { FromClause() [ WhereClause() ] + [ HierarchicalQueryClause() ] [ GroupByClause() ] [ OrderByClause() ] [ RowLimitingClause() ] @@ -1190,7 +1191,7 @@ void OrderByEntry() #void : { ( LOOKAHEAD(2) ColumnAlias() | LOOKAHEAD(2) SqlExpression() ) [ | ] - [ LOOKAHEAD(2) | LOOKAHEAD(2) ] + [ LOOKAHEAD(2) "FIRST" | LOOKAHEAD(2) "LAST" ] } /** @@ -1202,7 +1203,7 @@ ASTRowLimitingClause RowLimitingClause() : ( NumericLiteral() ( | ) | - ( | ) [ NumericLiteral() [ ] ] ( | ) ( | ) + ( "FIRST" | ) [ NumericLiteral() [ ] ] ( | ) ( | ) ) { return jjtThis; } } @@ -1215,7 +1216,7 @@ ASTQueryBlock QueryBlock() : SelectList() FromClause() [ WhereClause() ] - // [ HierarchicalQueryClause() ] + [ HierarchicalQueryClause() ] [ GroupByClause() ] // [ ModelClause() ] { return jjtThis; } @@ -1403,9 +1404,10 @@ ASTExpressionList ExpressionList() : ASTSqlExpression SqlExpression() : {} { + [ LOOKAHEAD(2) ] // SimpleExpression ( - LOOKAHEAD(FunctionCall()) FunctionCall() + LOOKAHEAD(3) AdditiveExpression() // this can be a literal or a simple expression, but no conditional | LOOKAHEAD(5) SchemaName() "." TableName() "." Column() | @@ -1414,37 +1416,24 @@ ASTSqlExpression SqlExpression() : LOOKAHEAD(2) Column() | LOOKAHEAD(2) - | - AdditiveExpression() // this can be a literal or a simple expression, but no conditional ) { return jjtThis; } } /** - * See also https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/About-User-Defined-Functions.html#GUID-4EB3E236-8216-471C-BA44-23D87BDFEA67 - * - * A function reference might be: - * function_name - * package.function_name - * package.schema.function_name - * optional: @ dblink . + * Built-in function call + * See https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/Functions.html#GUID-D079EFD3-C683-441F-977E-2C9503089982 */ ASTFunctionCall FunctionCall() : { ASTID id; - StringBuilder name = new StringBuilder(); } { - id = ID() { name.append(id.getImage()); } - [ "." id = ID() { name.append('.').append(id.getImage()); } - [ "." id = ID() { name.append('.').append(id.getImage()); } ] - ] - [ "@" id = ID() { name.append('@').append(id.getImage()); } ] - + id = ID() Arguments() { - jjtThis.setImage(name.toString()); + jjtThis.setImage(id.getImage()); return jjtThis; } } @@ -1456,6 +1445,20 @@ ASTColumn Column() : { return jjtThis; } } +/** + * https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__I2126079 + */ +ASTHierarchicalQueryClause HierarchicalQueryClause() : +{} +{ + ( + ( [ LOOKAHEAD(2) ] Condition() [ Condition() ] ) + | + ( Condition() [ LOOKAHEAD(2) ] Condition() ) + ) + { return jjtThis; } +} + ASTFromClause FromClause() : {} { @@ -1497,19 +1500,14 @@ ASTSelectList SelectList() : void SelectListEntry() #void : {} { - ( - LOOKAHEAD(3) TableAlias() "." "*" - | - LOOKAHEAD(FunctionCall()) FunctionCall() [ [] ColumnAlias() ] - | - LOOKAHEAD(3) Expression() [ [] ColumnAlias() ] - ) + [ LOOKAHEAD(2) ] + SqlExpression() [LOOKAHEAD(2, {!(getToken(1).kind == BULK)}) [LOOKAHEAD(2) ] ColumnAlias() ] } ASTColumnAlias ColumnAlias() : -{} +{ ASTID id; } { - ( | ) { jjtThis.setImage(token.image); } + id = ID() {jjtThis.setImage(id.getImage());} { return jjtThis; } } @@ -1957,7 +1955,7 @@ ASTMultiTableInsert MultiTableInsert() : ASTConditionalInsertClause ConditionalInsertClause() : {} { - [ | ] ( Condition() ( InsertIntoClause() [ ValuesClause() ] )+ )+ + [ | "FIRST" ] ( Condition() ( InsertIntoClause() [ ValuesClause() ] )+ )+ [ ( InsertIntoClause() [ ValuesClause() ] )+ ] { return jjtThis; } } @@ -2231,7 +2229,7 @@ ASTSubTypeDefinition SubTypeDefinition() : (( | | )["(" NumericLiteral() ")"] Datatype() ( )? ( Datatype())?) | - [ Datatype()] + "REF" [ Datatype()] //Enumeration | ( "(" Expression() @@ -2511,7 +2509,7 @@ ASTTrimExpression TrimExpression() : { Token thisToken; PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } { ( - (thisToken = ) { sb.append(thisToken.image);} + (thisToken = "TRIM" ) { sb.append(thisToken.image);} "(" { sb.append("(");} [ ( | | ){ sb.append(" "); sb.append(token.toString()); } ] [ simpleNode = StringExpression() { sb.append(" "); sb.append(simpleNode.getImage()); } ] @@ -2799,6 +2797,14 @@ ASTIsOfTypeCondition IsOfTypeCondition() #IsOfTypeCondition(>1) : * 2006-05-23 - Matthias Hendler - Added lookahead otherwise warning encountered. * Warning arised while adding methode triggerUnit(). * 2011-04-27 - SRT - Add optional NEW Keyword to cope with Object Type constructors + * + * See also https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/About-User-Defined-Functions.html#GUID-4EB3E236-8216-471C-BA44-23D87BDFEA67 + * + * A function reference might be: + * function_name + * package.function_name + * package.schema.function_name + * optional: @ dblink . */ ASTPrimaryExpression PrimaryExpression() #PrimaryExpression(>1) : { Token thisToken ; PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; @@ -2810,7 +2816,7 @@ ASTPrimaryExpression PrimaryExpression() #PrimaryExpression(>1) : ) | ( - LOOKAHEAD( PrimaryPrefix() ) // Lookahead so that we can recover and treat NEW as an identifier + LOOKAHEAD( PrimaryPrefix() ) // Lookahead so that we can recover and treat NEW as an identifier { sb.append(" NEW "); } (simpleNode = PrimaryPrefix() ) {sb.append(simpleNode.getImage());} | (simpleNode = PrimaryPrefix() ) {sb.append(simpleNode.getImage());} @@ -2829,7 +2835,9 @@ ASTPrimaryPrefix PrimaryPrefix() : } { ( - ( simpleNode = Literal() ) { sb.append(simpleNode.getImage()) ; } + LOOKAHEAD(2) ( + LOOKAHEAD(2) simpleNode = FunctionCall() +| LOOKAHEAD(2) simpleNode = Literal() ) { sb.append(simpleNode.getImage()) ; } | LOOKAHEAD(MultiSetCondition()) simpleNode = MultiSetCondition() | LOOKAHEAD(TrimExpression()) simpleNode = TrimExpression() //SRT 20110613.3 | LOOKAHEAD(CaseExpression()) ( simpleNode =CaseExpression() ) { sb.append(simpleNode.getImage()) ; } //SRT 20110520 @@ -3285,7 +3293,7 @@ ASTView View() : PLSQLNode simpleNode = null; } { - [ ] + [ "REPLACE"] [[] ] simpleNode = ObjectNameDeclaration() ["(" ViewColumn() ("," ViewColumn())* ")"] @@ -3302,7 +3310,7 @@ ASTSynonym Synonym() : PLSQLNode simpleNode = null; } { - [ ] + [ "REPLACE"] [] simpleNode = ObjectNameDeclaration() @@ -3317,7 +3325,7 @@ ASTDirectory Directory() : PLSQLNode simpleNode = null; } { - [ ] + [ "REPLACE"] simpleNode = ObjectNameDeclaration() @@ -3434,7 +3442,7 @@ ASTTypeSpecification TypeSpecification() : PLSQLNode simpleNode = null; } { - [ [ ] [ | ] ] + [ [ "REPLACE"] [ | ] ] simpleNode = ObjectNameDeclaration() [ @@ -3613,7 +3621,7 @@ ASTAlterTypeSpec AlterTypeSpec() : | */ [ - + "REPLACE" ( LOOKAHEAD(2) // ( | ) ( @@ -3825,7 +3833,7 @@ ASTTriggerUnit TriggerUnit() : PLSQLNode simpleNode = null ; } { - [ [ ] [ | ] ] + [ [ "REPLACE"] [ | ] ] () simpleNode = ObjectNameDeclaration() @@ -4071,7 +4079,6 @@ MORE : TOKEN [IGNORE_CASE]: { - | | | | @@ -4082,7 +4089,10 @@ TOKEN [IGNORE_CASE]: | // PRAGMA INLINE } -/* PL/SQL RESERVED WORDS */ +/** + * PL/SQL RESERVED WORDS + * https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-reserved-words-keywords.html#GUID-9BAA3A99-41B1-45CB-A91E-1E482BC1F927 + */ /** * 2006-05-20 - Matthias Hendler - Removed: * Added: , , , @@ -4106,7 +4116,6 @@ TOKEN [IGNORE_CASE]: | | | - | | | | @@ -4177,7 +4186,6 @@ TOKEN [IGNORE_CASE]: | | | - | | | | @@ -4221,7 +4229,6 @@ TOKEN [IGNORE_CASE]: | | // | - | | | | @@ -4233,11 +4240,9 @@ TOKEN [IGNORE_CASE]: | | | - | | | | - | | | | @@ -4311,7 +4316,6 @@ TOKEN [IGNORE_CASE]: | | | - | | | | @@ -4351,11 +4355,9 @@ TOKEN [IGNORE_CASE]: | | | - | | | | - | | | | @@ -4489,7 +4491,6 @@ TOKEN [IGNORE_CASE]: | | | -| | | | @@ -4510,9 +4511,10 @@ TOKEN [IGNORE_CASE]: | | | - | | +| +| } /** @@ -4596,7 +4598,11 @@ TOKEN : } -//SRT 2011-04-17 - START +/** + * PL/SQL Reserved words + * + * https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-reserved-words-keywords.html#GUID-9BAA3A99-41B1-45CB-A91E-1E482BC1F927 + */ ASTKEYWORD_RESERVED KEYWORD_RESERVED (): {} { // PL/SQL RESERVED WORDS - V$RESERVED.RESERVED='Y' @@ -4687,7 +4693,9 @@ ASTKEYWORD_RESERVED KEYWORD_RESERVED (): {} ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} { // PL/SQL UNRESERVED KEYWORDS - V$RESERVED.RESERVED='N' -( +( +"FIRST" | "REPLACE" | "REF" | "LAST" | "TRIM" | + | | //| @@ -4824,7 +4832,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| //| -//| +| //| //| //| @@ -4889,7 +4897,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| //| -//| //| //| //| @@ -4977,7 +4984,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| //| -| //| //| //| @@ -5098,7 +5104,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| | -| | //| //| @@ -5146,7 +5151,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| //| -| +//| //| //| //| @@ -5169,7 +5174,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| //| -| +//| //| //| //| @@ -5289,7 +5294,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| //| -//| +| //| //| //| @@ -5464,7 +5469,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| //| //| -| //| //| //| @@ -5481,7 +5485,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| //| -//| //| //| //| @@ -5674,7 +5677,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| | //| -| | //| //| @@ -5732,7 +5734,6 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | //| //| -//| | | | @@ -5798,7 +5799,6 @@ ASTID ID(): {} | | | | | | | | | | | | | */ - | //SYNTAX //20120501 | | | | | // | @@ -5810,7 +5810,7 @@ ASTID ID(): {} | //SYNTAX //RESERVED WORD | //RESERVED WORD //20120429 | | - | + //| // | | //RESERVED WORD | @@ -5827,7 +5827,7 @@ ASTID ID(): {} | //- | //20120501 | | //RESERVED WPRDS - | //SYNTAX //RESERVED WORD + //| //SYNTAX //RESERVED WORD | | //SYNTAX //RESERVED WORD //20120501 | @@ -5835,17 +5835,17 @@ ASTID ID(): {} | //RESERVED WORD | //SYNTAX | //RESERVED WORD - | <_DEFAULT> //RESERVED WORD + //| <_DEFAULT> //RESERVED WORD | //RESERVED WORD | //RESERVED WORD //| //RESERVED WORD | | //RESERVED WORD - | //SYNTAX //RESERVED WORD + //| //SYNTAX //RESERVED WORD | //SYNTAX //| | - | //SYNTAX + //| //SYNTAX | //SYNTAX //RESERVED WORD | //SYNTAX | //SYNTAX //RESERVED WORD @@ -5855,7 +5855,7 @@ ASTID ID(): {} | //SYNTAX //RESERVED WORD | //RESERVED WORD | //SYNTAX - | //RESERVED WORD + //| //RESERVED WORD // | | // this causes bug 643043 Procedure w/o params appears as variable | //SYNTAX @@ -5872,7 +5872,7 @@ ASTID ID(): {} | | //RESERVED WORD //20120501 | - | //RESERVED WORD + //| //RESERVED WORD | //SYNTAX //20120501 | | | | //RESERVED WORD @@ -5921,7 +5921,7 @@ ASTID ID(): {} | //SYNTAX | //RESERVED WORD //20120501 | - // | | | | + // | | | //| //20120501 | //20120501 | //SYNTAX @@ -5935,10 +5935,10 @@ ASTID ID(): {} | //RESERVED WORD | | | - | //RESERVED WORD - | // | + //| //RESERVED WORD + //| // | //20120501 | - | + //| | //RESERVED WORD | //|
//RESERVED WORD @@ -6002,7 +6002,6 @@ ASTUnqualifiedID UnqualifiedID(): {} | | | - | | | | @@ -6041,7 +6040,6 @@ ASTQualifiedID QualifiedID(): {} | --Unreserved Key Word | //SRT */ - | //20120501 | //| | @@ -6058,7 +6056,7 @@ ASTQualifiedID QualifiedID(): {} // // //20120429 | - | + //| // // | @@ -6183,7 +6181,6 @@ ASTQualifiedID QualifiedID(): {} | //20120501 | //| - //| //| //| //20120501 | @@ -6203,10 +6200,10 @@ ASTQualifiedID QualifiedID(): {} | | // - | + //| //| //20120501 | - | + //| | | //
@@ -6273,7 +6270,7 @@ ASTTypeKeyword TypeKeyword(): {} | | | - | | + | "REF" | | | | | | diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java index 9d86b884b8..6cfacdb9b3 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java @@ -986,4 +986,9 @@ public class PLSQLParserVisitorAdapter implements PLSQLParserVisitor { public Object visit(ASTValuesClause node, Object data) { return visit((PLSQLNode) node, data); } + + @Override + public Object visit(ASTHierarchicalQueryClause node, Object data) { + return visit((PLSQLNode) node, data); + } } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java index f43818e62d..28a593edac 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java @@ -1080,6 +1080,11 @@ public abstract class AbstractPLSQLRule extends AbstractRule implements PLSQLPar return visit((PLSQLNode) node, data); } + @Override + public Object visit(ASTHierarchicalQueryClause node, Object data) { + return visit((PLSQLNode) node, data); + } + /* * Treat all Executable Code */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatRule.java index db5d6031c4..30bd52b09a 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatRule.java @@ -125,10 +125,14 @@ public class CodeFormatRule extends AbstractPLSQLRule { int currentLine = firstLine; for (int i = 0; i < parent.jjtGetNumChildren(); i++) { Node child = parent.jjtGetChild(i); + String image = child.getImage(); + if (image == null && child.jjtGetNumChildren() > 0) { + image = child.jjtGetChild(0).getImage(); + } if (child.getBeginLine() != currentLine) { - addViolationWithMessage(data, child, child.getImage() + " should be on line " + currentLine); + addViolationWithMessage(data, child, image + " should be on line " + currentLine); } else if (i > 0 && child.getBeginColumn() != indentation) { - addViolationWithMessage(data, child, child.getImage() + " should begin at column " + indentation); + addViolationWithMessage(data, child, image + " should begin at column " + indentation); } // next entry needs to be on the next line currentLine++; diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectExpressionsTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectExpressionsTest.java index b6264dfe94..a988f0b9bd 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectExpressionsTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectExpressionsTest.java @@ -8,7 +8,6 @@ import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; @@ -16,7 +15,6 @@ import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; public class SelectExpressionsTest extends AbstractPLSQLParserTst { @Test - @Ignore public void parseSelectExpression() throws Exception { String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectExpressions.pls"), StandardCharsets.UTF_8); diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectHierarchicalTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectHierarchicalTest.java new file mode 100644 index 0000000000..f2809c982a --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectHierarchicalTest.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class SelectHierarchicalTest extends AbstractPLSQLParserTst { + + @Test + public void parseSelectHierarchicalQueries() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectHierarchical.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + Assert.assertNotNull(input); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java index 18d549472a..7edba49d3b 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java @@ -23,8 +23,12 @@ public class WhereClauseTest extends AbstractPLSQLParserTst { List selectStatements = input.findDescendantsOfType(ASTSelectIntoStatement.class); Assert.assertEquals(3, selectStatements.size()); - ASTFunctionCall functionCall = selectStatements.get(2).getFirstDescendantOfType(ASTFunctionCall.class); - Assert.assertEquals("utils.get_colname", functionCall.getImage()); + ASTFunctionCall functionCall = selectStatements.get(0).getFirstDescendantOfType(ASTFunctionCall.class); + Assert.assertEquals("UPPER", functionCall.getImage()); + + ASTPrimaryPrefix primaryPrefix = selectStatements.get(2).getFirstDescendantOfType(ASTWhereClause.class) + .findDescendantsOfType(ASTPrimaryPrefix.class).get(1); + Assert.assertEquals("utils.get_colname", primaryPrefix.getImage()); } @Test diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls index 06641367bd..a8084fd395 100644 --- a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls @@ -4,6 +4,9 @@ BEGIN +SELECT AVG(sal)*2 INTO foo FROM bar; + + SELECT AVG(salary) * 12 "Average Sal" INTO some_record @@ -14,5 +17,6 @@ SELECT INTO some_record FROM some_table; + END; / \ No newline at end of file diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectHierarchical.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectHierarchical.pls new file mode 100644 index 0000000000..84b7b20516 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectHierarchical.pls @@ -0,0 +1,90 @@ +-- +-- Select statement with hierarchical queries +-- +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6 +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__I2130004 +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/Hierarchical-Queries.html#GUID-0118DF1D-B9A9-41EB-8556-C6E7D6A5A84E +-- + +BEGIN + +SELECT id INTO v_id + FROM (SELECT separator_in || string_in || separator_in AS token_list FROM DUAL) + CONNECT BY col_length <= LENGTH(string_in) - LENGTH(separator_in); + +SELECT last_name, employee_id, manager_id + INTO test + FROM employees + CONNECT BY employee_id = manager_id + ORDER BY last_name; + +SELECT last_name, employee_id, manager_id + INTO test + FROM employees + CONNECT BY PRIOR employee_id = manager_id + AND salary > commission_pct + ORDER BY last_name; + +SELECT employee_id, last_name, manager_id + INTO test + FROM employees + CONNECT BY PRIOR employee_id = manager_id; + +SELECT employee_id, last_name, manager_id, LEVEL + INTO test + FROM employees + CONNECT BY PRIOR employee_id = manager_id; + +SELECT last_name, employee_id, manager_id, LEVEL + INTO test + FROM employees + START WITH employee_id = 100 + CONNECT BY PRIOR employee_id = manager_id + ORDER SIBLINGS BY last_name; + +SELECT last_name "Employee", + LEVEL, SYS_CONNECT_BY_PATH(last_name, '/') "Path" + INTO test + FROM employees + WHERE level <= 3 AND department_id = 80 + START WITH last_name = 'King' + CONNECT BY PRIOR employee_id = manager_id AND LEVEL <= 4; + +SELECT last_name "Employee", CONNECT_BY_ISCYCLE "Cycle", + LEVEL, SYS_CONNECT_BY_PATH(last_name, '/') "Path" + INTO test + FROM employees + WHERE level <= 3 AND department_id = 80 + START WITH last_name = 'King' + CONNECT BY NOCYCLE PRIOR employee_id = manager_id AND LEVEL <= 4 + ORDER BY "Employee", "Cycle", LEVEL, "Path"; + +SELECT LTRIM(SYS_CONNECT_BY_PATH (warehouse_id,','),',') + INTO test + FROM + (SELECT ROWNUM r, warehouse_id FROM warehouses) + WHERE CONNECT_BY_ISLEAF = 1 + START WITH r = 1 + CONNECT BY r = PRIOR r + 1 + ORDER BY warehouse_id; + +SELECT last_name "Employee", CONNECT_BY_ROOT last_name "Manager", + LEVEL-1 "Pathlen", SYS_CONNECT_BY_PATH(last_name, '/') "Path" + INTO test + FROM employees + WHERE LEVEL > 1 and department_id = 110 + CONNECT BY PRIOR employee_id = manager_id + ORDER BY "Employee", "Manager", "Pathlen", "Path"; + +SELECT name, SUM(salary) "Total_Salary" + INTO test + FROM ( + SELECT CONNECT_BY_ROOT last_name as name, Salary + FROM employees + WHERE department_id = 110 + CONNECT BY PRIOR employee_id = manager_id) + GROUP BY name + ORDER BY name, "Total_Salary"; + +END; +/