[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.
This commit is contained in:
Andreas Dangel
2019-01-25 15:34:06 +01:00
committed by Andreas Dangel
parent 544238244b
commit fcba29a3b7
9 changed files with 227 additions and 96 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -23,8 +23,12 @@ public class WhereClauseTest extends AbstractPLSQLParserTst {
List<ASTSelectIntoStatement> 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

View File

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

View File

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