From 7cd62c0eb770d691c7b4d63d4fce70a15dc66b31 Mon Sep 17 00:00:00 2001 From: wenshao Date: Fri, 12 May 2017 10:46:30 +0800 Subject: [PATCH] improved oracle pl/sql parser. --- .../sql/ast/statement/SQLOpenStatement.java | 21 ++- .../oracle/parser/OracleStatementParser.java | 6 + .../druid/sql/parser/SQLStatementParser.java | 13 ++ .../sql/visitor/SQLASTOutputVisitor.java | 8 + .../sql/oracle/pl/Oracle_pl_open_for_0.java | 168 ++++++++++++++++++ 5 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_0.java diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLOpenStatement.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLOpenStatement.java index 37b4f3db47..ef834aef59 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLOpenStatement.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLOpenStatement.java @@ -15,6 +15,7 @@ */ package com.alibaba.druid.sql.ast.statement; +import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLStatementImpl; import com.alibaba.druid.sql.visitor.SQLASTVisitor; /** @@ -24,7 +25,9 @@ import com.alibaba.druid.sql.visitor.SQLASTVisitor; public class SQLOpenStatement extends SQLStatementImpl{ //cursor name - private String cursorName; + private String cursorName; + + private SQLExpr forExpr; public String getCursorName() { return cursorName; @@ -36,10 +39,20 @@ public class SQLOpenStatement extends SQLStatementImpl{ @Override protected void accept0(SQLASTVisitor visitor) { - // TODO Auto-generated method stub - visitor.visit(this); + if (visitor.visit(this)) { + acceptChild(visitor, forExpr); + } visitor.endVisit(this); - } + public SQLExpr getFor() { + return forExpr; + } + + public void setFor(SQLExpr forExpr) { + if (forExpr != null) { + forExpr.setParent(this); + } + this.forExpr = forExpr; + } } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleStatementParser.java b/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleStatementParser.java index c24246e4c7..2365e764e9 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleStatementParser.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleStatementParser.java @@ -422,6 +422,12 @@ public class OracleStatementParser extends SQLStatementParser { continue; } + if (lexer.token() == Token.CLOSE) { + SQLStatement stmt = this.parseClose(); + statementList.add(stmt); + continue; + } + if (lexer.token() == Token.CASE) { SQLStatement stmt = this.parseCase(); statementList.add(stmt); diff --git a/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java b/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java index b29e0168e8..1fbe661029 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java @@ -26,6 +26,7 @@ import com.alibaba.druid.sql.ast.SQLObject; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.expr.SQLCharExpr; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLQueryExpr; import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; import com.alibaba.druid.sql.ast.statement.*; import com.alibaba.druid.sql.ast.statement.SQLCreateTriggerStatement.TriggerEvent; @@ -2189,6 +2190,18 @@ public class SQLStatementParser extends SQLParser { SQLOpenStatement stmt = new SQLOpenStatement(); accept(Token.OPEN); stmt.setCursorName(exprParser.name().getSimpleName()); + + if (lexer.token() == Token.FOR) { + lexer.nextToken(); + if (lexer.token() == Token.SELECT) { + SQLSelectParser selectParser = createSQLSelectParser(); + SQLSelect select = selectParser.select(); + SQLQueryExpr queryExpr = new SQLQueryExpr(select); + stmt.setFor(queryExpr); + } else { + throw new ParserException("TODO " + lexer.info()); + } + } accept(Token.SEMI); return stmt; } diff --git a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java index 3607e27e90..75d4bfe449 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -1025,6 +1025,8 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Paramet x.getSubQuery().accept(this); print(')'); println(); + } else if (parent instanceof SQLOpenStatement) { + x.getSubQuery().accept(this); } else { print('('); incrementIndent(); @@ -3201,6 +3203,12 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Paramet public boolean visit(SQLOpenStatement x) { print0(ucase ? "OPEN " : "open "); print0(x.getCursorName()); + + SQLExpr forExpr = x.getFor(); + if (forExpr != null) { + print0(ucase ? " FOR " : "for "); + forExpr.accept(this); + } return false; } diff --git a/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_0.java b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_0.java new file mode 100644 index 0000000000..21cd820e93 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_0.java @@ -0,0 +1,168 @@ +/* + * Copyright 1999-2017 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.druid.bvt.sql.oracle.pl; + +import com.alibaba.druid.sql.OracleTest; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.visitor.SchemaStatVisitor; +import com.alibaba.druid.util.JdbcConstants; + +import java.util.List; + +public class Oracle_pl_open_for_0 extends OracleTest { + + public void test_0() throws Exception { + String sql = "DECLARE\n" + + " sal employees.salary%TYPE;\n" + + " sal_multiple employees.salary%TYPE;\n" + + " factor INTEGER := 2;\n" + + " \n" + + " cv SYS_REFCURSOR;\n" + + " \n" + + "BEGIN\n" + + " DBMS_OUTPUT.PUT_LINE('factor = ' || factor);\n" + + " \n" + + " OPEN cv FOR\n" + + " SELECT salary, salary*factor\n" + + " FROM employees\n" + + " WHERE job_id LIKE 'AD_%'; -- PL/SQL evaluates factor\n" + + " \n" + + " LOOP\n" + + " FETCH cv INTO sal, sal_multiple;\n" + + " EXIT WHEN cv%NOTFOUND;\n" + + " DBMS_OUTPUT.PUT_LINE('sal = ' || sal);\n" + + " DBMS_OUTPUT.PUT_LINE('sal_multiple = ' || sal_multiple);\n" + + " END LOOP;\n" + + " \n" + + " factor := factor + 1;\n" + + " \n" + + " DBMS_OUTPUT.PUT_LINE('factor = ' || factor);\n" + + " \n" + + " OPEN cv FOR\n" + + " SELECT salary, salary*factor\n" + + " FROM employees\n" + + " WHERE job_id LIKE 'AD_%'; -- PL/SQL evaluates factor\n" + + " \n" + + " LOOP\n" + + " FETCH cv INTO sal, sal_multiple;\n" + + " EXIT WHEN cv%NOTFOUND;\n" + + " DBMS_OUTPUT.PUT_LINE('sal = ' || sal);\n" + + " DBMS_OUTPUT.PUT_LINE('sal_multiple = ' || sal_multiple);\n" + + " END LOOP;\n" + + " \n" + + " CLOSE cv;\n" + + "END;"; // + + List statementList = SQLUtils.parseStatements(sql, JdbcConstants.ORACLE); + SQLStatement stmt = statementList.get(0); + + assertEquals(1, statementList.size()); + + SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(JdbcConstants.ORACLE); + for (SQLStatement statement : statementList) { + statement.accept(visitor); + } + +// System.out.println("Tables : " + visitor.getTables()); +// System.out.println("fields : " + visitor.getColumns()); +// System.out.println("coditions : " + visitor.getConditions()); +// System.out.println("relationships : " + visitor.getRelationships()); +// System.out.println("orderBy : " + visitor.getOrderByColumns()); + + assertEquals(0, visitor.getTables().size()); + +// Assert.assertTrue(visitor.getTables().containsKey(new TableStat.Name("employees"))); +// Assert.assertTrue(visitor.getTables().containsKey(new TableStat.Name("emp_name"))); + +// Assert.assertEquals(7, visitor.getColumns().size()); +// Assert.assertEquals(3, visitor.getConditions().size()); +// Assert.assertEquals(1, visitor.getRelationships().size()); + + // Assert.assertTrue(visitor.getColumns().contains(new TableStat.Column("employees", "salary"))); + + { + String output = SQLUtils.toOracleString(stmt); + assertEquals("DECLARE\n" + + "\tsal employees.salary%TYPE;\n" + + "\tsal_multiple employees.salary%TYPE;\n" + + "\tfactor INTEGER := 2;\n" + + "\tcv SYS_REFCURSOR;\n" + + "BEGIN\n" + + "\tDBMS_OUTPUT.PUT_LINE('factor = ' || factor);\n" + + "\tOPEN cv FOR \n" + + "\t\tSELECT salary, salary * factor\n" + + "\t\tFROM employees\n" + + "\t\tWHERE job_id LIKE 'AD_%';\n" + + "\tLOOP\n" + + "\t\tFETCH cv INTO sal, sal_multiple;\n" + + "\t\tEXIT WHEN cv % NOTFOUND;\n" + + "\t\tDBMS_OUTPUT.PUT_LINE('sal = ' || sal);\n" + + "\t\tDBMS_OUTPUT.PUT_LINE('sal_multiple = ' || sal_multiple);\n" + + "\tEND LOOP;\n" + + "\tfactor := factor + 1;\n" + + "\tDBMS_OUTPUT.PUT_LINE('factor = ' || factor);\n" + + "\tOPEN cv FOR \n" + + "\t\tSELECT salary, salary * factor\n" + + "\t\tFROM employees\n" + + "\t\tWHERE job_id LIKE 'AD_%';\n" + + "\tLOOP\n" + + "\t\tFETCH cv INTO sal, sal_multiple;\n" + + "\t\tEXIT WHEN cv % NOTFOUND;\n" + + "\t\tDBMS_OUTPUT.PUT_LINE('sal = ' || sal);\n" + + "\t\tDBMS_OUTPUT.PUT_LINE('sal_multiple = ' || sal_multiple);\n" + + "\tEND LOOP;\n" + + "\tCLOSE cv;\n" + + "END", // + output); + } + { + String output = SQLUtils.toOracleString(stmt, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION); + assertEquals("declare\n" + + "\tsal employees.salary%TYPE;\n" + + "\tsal_multiple employees.salary%TYPE;\n" + + "\tfactor INTEGER := 2;\n" + + "\tcv SYS_REFCURSOR;\n" + + "begin\n" + + "\tDBMS_OUTPUT.PUT_LINE('factor = ' || factor);\n" + + "\topen cvfor \n" + + "\t\tselect salary, salary * factor\n" + + "\t\tfrom employees\n" + + "\t\twhere job_id like 'AD_%';\n" + + "\tloop\n" + + "\t\tfetch cv into sal, sal_multiple;\n" + + "\t\texit when cv % NOTFOUND;\n" + + "\t\tDBMS_OUTPUT.PUT_LINE('sal = ' || sal);\n" + + "\t\tDBMS_OUTPUT.PUT_LINE('sal_multiple = ' || sal_multiple);\n" + + "\tend loop;\n" + + "\tfactor := factor + 1;\n" + + "\tDBMS_OUTPUT.PUT_LINE('factor = ' || factor);\n" + + "\topen cvfor \n" + + "\t\tselect salary, salary * factor\n" + + "\t\tfrom employees\n" + + "\t\twhere job_id like 'AD_%';\n" + + "\tloop\n" + + "\t\tfetch cv into sal, sal_multiple;\n" + + "\t\texit when cv % NOTFOUND;\n" + + "\t\tDBMS_OUTPUT.PUT_LINE('sal = ' || sal);\n" + + "\t\tDBMS_OUTPUT.PUT_LINE('sal_multiple = ' || sal_multiple);\n" + + "\tend loop;\n" + + "\tclose cv;\n" + + "end", // + output); + } + } +}