diff --git a/src/main/java/com/alibaba/druid/sql/SQLUtils.java b/src/main/java/com/alibaba/druid/sql/SQLUtils.java index 9f2bfc291e..a3a1e15fa4 100644 --- a/src/main/java/com/alibaba/druid/sql/SQLUtils.java +++ b/src/main/java/com/alibaba/druid/sql/SQLUtils.java @@ -292,11 +292,15 @@ public class SQLUtils { visitor.setTableMapping(tableMapping); } - for (int i = 0; i < statementList.size(); i++) { + boolean printStmtSeperator = !JdbcConstants.ORACLE.equals(dbType); + + for (int i = 0, size = statementList.size(); i < size; i++) { SQLStatement stmt = statementList.get(i); if (i > 0) { - visitor.print(";"); + if (printStmtSeperator) { + visitor.print(";"); + } SQLStatement preStmt = statementList.get(i - 1); List comments = preStmt.getAfterCommentsDirect(); @@ -309,7 +313,10 @@ public class SQLUtils { visitor.print(comment); } } - visitor.println(); + + if (printStmtSeperator) { + visitor.println(); + } if (!(stmt instanceof SQLSetStatement)) { visitor.println(); @@ -325,12 +332,9 @@ public class SQLUtils { } stmt.accept(visitor); - if (i == statementList.size() - 1) { + if (i == size - 1) { Boolean semi = (Boolean) stmt.getAttribute("format.semi"); - if (semi != null && semi.booleanValue()) { -// if (stmt.hasAfterComment()) { -// visitor.println(); -// } + if (semi != null && semi.booleanValue() && printStmtSeperator) { visitor.print(";"); } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/stmt/OracleForStatement.java b/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/stmt/OracleForStatement.java index a0ecb1b9cc..6858060fb4 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/stmt/OracleForStatement.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/stmt/OracleForStatement.java @@ -31,6 +31,8 @@ public class OracleForStatement extends OracleStatementImpl { private List statements = new ArrayList(); + private boolean all; + @Override public void accept0(OracleASTVisitor visitor) { if (visitor.visit(this)) { @@ -65,4 +67,11 @@ public class OracleForStatement extends OracleStatementImpl { this.statements = statements; } + public boolean isAll() { + return all; + } + + public void setAll(boolean all) { + this.all = all; + } } 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 5b41bd7ced..de6e6e343f 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 @@ -210,11 +210,18 @@ public class OracleStatementParser extends SQLStatementParser { } if (lexer.token() == Token.IDENTIFIER) { - if (lexer.stringVal().equals("RAISE")) { + String strVal = lexer.stringVal(); + if (strVal.equalsIgnoreCase("RAISE")) { statementList.add(this.parseRaise()); continue; } + if (strVal.equalsIgnoreCase("FORALL")) { + SQLStatement forAll = this.parseFor(); + statementList.add(forAll); + continue; + } + SQLExpr expr = exprParser.expr(); OracleExprStatement stmt = new OracleExprStatement(expr); statementList.add(stmt); @@ -547,18 +554,30 @@ public class OracleStatementParser extends SQLStatementParser { } public OracleForStatement parseFor() { - accept(Token.FOR); - OracleForStatement stmt = new OracleForStatement(); + if (lexer.token() == Token.FOR) { + lexer.nextToken(); + } else { + acceptIdentifier("FORALL"); + stmt.setAll(true); + } + stmt.setIndex(this.exprParser.name()); accept(Token.IN); stmt.setRange(this.exprParser.expr()); - accept(Token.LOOP); - this.parseStatementList(stmt.getStatements()); - accept(Token.END); - accept(Token.LOOP); + if (stmt.isAll()) { + SQLStatement itemStmt = this.parseStatement(); + itemStmt.setParent(stmt); + stmt.getStatements().add(itemStmt); + } else { + accept(Token.LOOP); + + this.parseStatementList(stmt.getStatements()); + accept(Token.END); + accept(Token.LOOP); + } return stmt; } @@ -1122,8 +1141,22 @@ public class OracleStatementParser extends SQLStatementParser { acceptIdentifier("TYPE"); } - String typeName = name.toString() + "%TYPE"; + String typeName = "TABLE OF " + name.toString() + "%TYPE"; dataType = new SQLDataTypeImpl(typeName); + } else if (identifierEquals("VARRAY")) { + lexer.nextToken(); + accept(Token.LPAREN); + int len = this.exprParser.acceptInteger(); + accept(Token.RPAREN); + accept(Token.OF); + + if (identifierEquals("NUMBER")) { + lexer.nextToken(); + String typeName = "VARRAY(" + len + ") OF NUMBER"; + dataType = new SQLDataTypeImpl(typeName); + } else { + throw new ParserException("TODO : " + lexer.info()); + } } else { throw new ParserException("TODO : " + lexer.info()); } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/oracle/visitor/OracleOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/oracle/visitor/OracleOutputVisitor.java index 2abe816189..8f069a0f53 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/oracle/visitor/OracleOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/oracle/visitor/OracleOutputVisitor.java @@ -112,12 +112,21 @@ public class OracleOutputVisitor extends SQLASTOutputVisitor implements OracleAS if (x instanceof OraclePLSQLCommitStatement) { return; } - if (x.getParent() instanceof SQLCreateProcedureStatement) { + + SQLObject parent = x.getParent(); + if (parent instanceof SQLCreateProcedureStatement) { return; } if (isPrettyFormat()) { - if (x.getParent() != null) { + if (parent instanceof OracleForStatement) { + OracleForStatement forStatement = (OracleForStatement) parent; + if (forStatement.isAll()) { + return; + } + } + + if (parent != null) { print(';'); } else { println(";"); @@ -1857,28 +1866,39 @@ public class OracleOutputVisitor extends SQLASTOutputVisitor implements OracleAS @Override public boolean visit(OracleForStatement x) { - print0(ucase ? "FOR " : "for "); + boolean all = x.isAll(); + if (all) { + print0(ucase ? "FORALL " : "forall "); + } else { + print0(ucase ? "FOR " : "for "); + } x.getIndex().accept(this); print0(ucase ? " IN " : " in "); x.getRange().accept(this); - println(); - print0(ucase ? "LOOP" : "loop"); + if (!all) { + println(); + print0(ucase ? "LOOP" : "loop"); + } incrementIndent(); println(); for (int i = 0, size = x.getStatements().size(); i < size; ++i) { SQLStatement stmt = x.getStatements().get(i); stmt.accept(this); - if (i != size - 1) { - println(";"); - } else { - print(';'); + if (!all) { + if (i != size - 1) { + println(";"); + } else { + print(';'); + } } } decrementIndent(); - println(); - print0(ucase ? "END LOOP" : "end loop"); + if (!all) { + println(); + print0(ucase ? "END LOOP" : "end loop"); + } return false; } diff --git a/src/main/java/com/alibaba/druid/sql/parser/SQLParser.java b/src/main/java/com/alibaba/druid/sql/parser/SQLParser.java index 73bd72db15..14166d42cc 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLParser.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLParser.java @@ -270,6 +270,17 @@ public class SQLParser { } } + public int acceptInteger() { + if (lexer.token() == Token.LITERAL_INT) { + int intVal = ((Integer) lexer.integerValue()).intValue(); + lexer.nextToken(); + return intVal; + } else { + throw new ParserException("syntax error, expect int, actual " + lexer.token() + " " + + lexer.stringVal()); + } + } + public void match(Token token) { if (lexer.token() != token) { throw new ParserException("syntax error, expect " + token + ", actual " + lexer.token() + " " 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 fd24b1a305..1737f1f89e 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -3353,8 +3353,10 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Paramet if (JdbcConstants.ORACLE.equals(dbType)) { String dataTypeName = dataType.getName(); - if ((dataTypeName.endsWith("%TYPE") && x.getDefaultValue() == null) - || dataTypeName.equalsIgnoreCase("REF CURSOR")) { + boolean printType = (dataTypeName.startsWith("TABLE OF") && x.getDefaultValue() == null) + || dataTypeName.equalsIgnoreCase("REF CURSOR") + || dataTypeName.startsWith("VARRAY("); + if (printType) { print0(ucase ? "TYPE " : "type "); } @@ -3372,6 +3374,10 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Paramet if (x.isNoCopy()) { print0(ucase ? "NOCOPY " : "nocopy "); } + + if (printType) { + print0(ucase ? "IS " : "is "); + } } else { if (x.getParamType() == SQLParameter.ParameterType.IN) { print0(ucase ? "IN " : "in "); diff --git a/src/main/java/com/alibaba/druid/support/http/StatViewFilter.java b/src/main/java/com/alibaba/druid/support/http/StatViewFilter.java new file mode 100644 index 0000000000..ace523a722 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/http/StatViewFilter.java @@ -0,0 +1,24 @@ +package com.alibaba.druid.support.http; + +import javax.servlet.*; +import java.io.IOException; + +/** + * Created by wenshao on 12/05/2017. + */ +public class StatViewFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + } + + @Override + public void destroy() { + + } +} diff --git a/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_1.java b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_1.java index 18ab232f41..ddcfac39b1 100644 --- a/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_1.java +++ b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_1.java @@ -80,9 +80,9 @@ public class Oracle_pl_open_for_1 extends OracleTest { String output = SQLUtils.toOracleString(stmt); System.out.println(output); assertEquals("DECLARE\n" + - "\tTYPE empcurtyp REF CURSOR;\n" + - "\tTYPE employees.last_name employees.last_name%TYPE;\n" + - "\tTYPE employees.salary employees.salary%TYPE;\n" + + "\tTYPE empcurtyp IS REF CURSOR;\n" + + "\tTYPE employees.last_name IS TABLE OF employees.last_name%TYPE;\n" + + "\tTYPE employees.salary IS TABLE OF employees.salary%TYPE;\n" + "\temp_cv empcurtyp;\n" + "\tnames namelist;\n" + "\tsals sallist;\n" + @@ -104,9 +104,9 @@ public class Oracle_pl_open_for_1 extends OracleTest { { String output = SQLUtils.toOracleString(stmt, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION); assertEquals("declare\n" + - "\ttype empcurtyp REF CURSOR;\n" + - "\ttype employees.last_name employees.last_name%TYPE;\n" + - "\ttype employees.salary employees.salary%TYPE;\n" + + "\ttype empcurtyp is REF CURSOR;\n" + + "\ttype employees.last_name is TABLE OF employees.last_name%TYPE;\n" + + "\ttype employees.salary is TABLE OF employees.salary%TYPE;\n" + "\temp_cv empcurtyp;\n" + "\tnames namelist;\n" + "\tsals sallist;\n" + diff --git a/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_2.java b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_2.java new file mode 100644 index 0000000000..5943e80e80 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_2.java @@ -0,0 +1,117 @@ +/* + * 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_2 extends OracleTest { + + public void test_0() throws Exception { + String sql = "DROP TABLE employees_temp;\n" + + "CREATE TABLE employees_temp AS SELECT * FROM employees;\n" + + "\n" + + "DECLARE\n" + + " TYPE NumList IS VARRAY(20) OF NUMBER;\n" + + " depts NumList := NumList(10, 30, 70); -- department numbers\n" + + "BEGIN\n" + + " FOR i IN depts.FIRST..depts.LAST LOOP\n" + + " DELETE FROM employees_temp\n" + + " WHERE department_id = depts(i);\n" + + " END LOOP;\n" + + "END;"; // + + List statementList = SQLUtils.parseStatements(sql, JdbcConstants.ORACLE); + assertEquals(3, statementList.size()); + + SQLStatement stmt = statementList.get(0); + + + + 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(2, 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.toSQLString(statementList, JdbcConstants.ORACLE); + System.out.println(output); + assertEquals("DROP TABLE employees_temp;\n" + + "\n" + + "CREATE TABLE employees_temp\n" + + "AS\n" + + "SELECT *\n" + + "FROM employees;\n" + + "\n" + + "DECLARE\n" + + "\tTYPE NumList IS VARRAY(20) OF NUMBER;\n" + + "\tdepts NumList := NumList(10, 30, 70);\n" + + "BEGIN\n" + + "\tFOR i IN depts.FIRST..depts.LAST\n" + + "\tLOOP\n" + + "\t\tDELETE FROM employees_temp\n" + + "\t\tWHERE department_id = depts(i);\n" + + "\t\t;\n" + + "\tEND LOOP;\n" + + "END;\n", // + output); + } + { + String output = SQLUtils.toSQLString(statementList, JdbcConstants.ORACLE, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION); + assertEquals("drop table employees_temp;\n" + + "\n" + + "create table employees_temp\n" + + "as\n" + + "select *\n" + + "from employees;\n" + + "\n" + + "declare\n" + + "\ttype NumList is VARRAY(20) OF NUMBER;\n" + + "\tdepts NumList := NumList(10, 30, 70);\n" + + "begin\n" + + "\tfor i in depts.FIRST..depts.LAST\n" + + "\tloop\n" + + "\t\tdelete from employees_temp\n" + + "\t\twhere department_id = depts(i);\n" + + "\t\t;\n" + + "\tend loop;\n" + + "end;\n", // + output); + } + } +} diff --git a/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_forall_0.java b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_forall_0.java new file mode 100644 index 0000000000..e759ff7c84 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_forall_0.java @@ -0,0 +1,105 @@ +/* + * 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_forall_0 extends OracleTest { + + public void test_0() throws Exception { + String sql = "DROP TABLE employees_temp;\n" + + "CREATE TABLE employees_temp AS SELECT * FROM employees;\n" + + "\n" + + "DECLARE\n" + + " TYPE NumList IS VARRAY(20) OF NUMBER;\n" + + " depts NumList := NumList(10, 30, 70); -- department numbers\n" + + "BEGIN\n" + + " FORALL i IN depts.FIRST..depts.LAST\n" + + " DELETE FROM employees_temp\n" + + " WHERE department_id = depts(i);\n" + + "END;"; // + + List statementList = SQLUtils.parseStatements(sql, JdbcConstants.ORACLE); + assertEquals(3, 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(2, 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.toSQLString(statementList, JdbcConstants.ORACLE); + System.out.println(output); + assertEquals("DROP TABLE employees_temp;\n" + + "\n" + + "CREATE TABLE employees_temp\n" + + "AS\n" + + "SELECT *\n" + + "FROM employees;\n" + + "\n" + + "DECLARE\n" + + "\tTYPE NumList IS VARRAY(20) OF NUMBER;\n" + + "\tdepts NumList := NumList(10, 30, 70);\n" + + "BEGIN\n" + + "\tFORALL i IN depts.FIRST..depts.LAST\n" + + "\t\tDELETE FROM employees_temp\n" + + "\t\tWHERE department_id = depts(i);\n" + + "END;\n", // + output); + } + { + String output = SQLUtils.toSQLString(statementList, JdbcConstants.ORACLE, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION); + assertEquals("drop table employees_temp;\n" + + "\n" + + "create table employees_temp\n" + + "as\n" + + "select *\n" + + "from employees;\n" + + "\n" + + "declare\n" + + "\ttype NumList is VARRAY(20) OF NUMBER;\n" + + "\tdepts NumList := NumList(10, 30, 70);\n" + + "begin\n" + + "\tforall i in depts.FIRST..depts.LAST\n" + + "\t\tdelete from employees_temp\n" + + "\t\twhere department_id = depts(i);\n" + + "end;\n", // + output); + } + } +}