diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLFetchStatement.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLFetchStatement.java index 4275f409f5..5670c78450 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLFetchStatement.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLFetchStatement.java @@ -27,6 +27,8 @@ public class SQLFetchStatement extends SQLStatementImpl { private SQLName cursorName; + private boolean bulkCollect; + private List into = new ArrayList(); @Override @@ -54,4 +56,11 @@ public class SQLFetchStatement extends SQLStatementImpl { this.into = into; } + public boolean isBulkCollect() { + return bulkCollect; + } + + public void setBulkCollect(boolean bulkCollect) { + this.bulkCollect = bulkCollect; + } } 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 2365e764e9..5b41bd7ced 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 @@ -1085,23 +1085,50 @@ public class OracleStatementParser extends SQLStatementParser { for (;;) { SQLParameter parameter = new SQLParameter(); + SQLName name; + SQLDataType dataType; if (lexer.token() == Token.CURSOR) { lexer.nextToken(); - parameter.setName(this.exprParser.name()); + name = this.exprParser.name(); accept(Token.IS); SQLSelect select = this.createSQLSelectParser().select(); - SQLDataTypeImpl dataType = new SQLDataTypeImpl(); + dataType = new SQLDataTypeImpl(); dataType.setName("CURSOR"); - parameter.setDataType(dataType); parameter.setDefaultValue(new SQLQueryExpr(select)); } else if (lexer.token() == Token.PROCEDURE) { break; + } else if (identifierEquals("TYPE")) { + lexer.nextToken(); + name = this.exprParser.name(); + accept(Token.IS); + + if (identifierEquals("REF")) { + lexer.nextToken(); + accept(Token.CURSOR); + + dataType = new SQLDataTypeImpl("REF CURSOR"); + } else if (lexer.token() == Token.TABLE) { + lexer.nextToken(); + accept(Token.OF); + + name = this.exprParser.name(); + + if (lexer.token() == Token.PERCENT) { + lexer.nextToken(); + acceptIdentifier("TYPE"); + } + + String typeName = name.toString() + "%TYPE"; + dataType = new SQLDataTypeImpl(typeName); + } else { + throw new ParserException("TODO : " + lexer.info()); + } } else { - parameter.setName(this.exprParser.name()); + name = this.exprParser.name(); if (lexer.token() == Token.IN) { lexer.nextToken(); @@ -1122,7 +1149,7 @@ public class OracleStatementParser extends SQLStatementParser { parameter.setNoCopy(true); } - parameter.setDataType(this.exprParser.parseDataType()); + dataType = this.exprParser.parseDataType(); if (lexer.token() == Token.COLONEQ) { lexer.nextToken(); @@ -1130,6 +1157,9 @@ public class OracleStatementParser extends SQLStatementParser { } } + parameter.setName(name); + parameter.setDataType(dataType); + parameters.add(parameter); if (lexer.token() == Token.COMMA || lexer.token() == Token.SEMI) { lexer.nextToken(); 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 1fbe661029..55fb7d6133 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java @@ -2211,6 +2211,13 @@ public class SQLStatementParser extends SQLParser { SQLFetchStatement stmt = new SQLFetchStatement(); stmt.setCursorName(this.exprParser.name()); + + if (identifierEquals("BULK")) { + lexer.nextToken(); + acceptIdentifier("COLLECT"); + stmt.setBulkCollect(true); + } + accept(Token.INTO); for (;;) { stmt.getInto().add(this.exprParser.name()); 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 75d4bfe449..1949b3689e 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -3216,7 +3216,11 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Paramet public boolean visit(SQLFetchStatement x) { print0(ucase ? "FETCH " : "fetch "); x.getCursorName().accept(this); - print0(ucase ? " INTO " : " into "); + if (x.isBulkCollect()) { + print0(ucase ? " BULK COLLECT INTO " : " bulk collect into "); + } else { + print0(ucase ? " INTO " : " into "); + } printAndAccept(x.getInto(), ", "); return false; } 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 new file mode 100644 index 0000000000..1ba1af8e7d --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_open_for_1.java @@ -0,0 +1,128 @@ +/* + * 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_1 extends OracleTest { + + public void test_0() throws Exception { + String sql = "DECLARE\n" + + " TYPE empcurtyp IS REF CURSOR;\n" + + " TYPE namelist IS TABLE OF employees.last_name%TYPE;\n" + + " TYPE sallist IS TABLE OF employees.salary%TYPE;\n" + + " emp_cv empcurtyp;\n" + + " names namelist;\n" + + " sals sallist;\n" + + "BEGIN\n" + + " OPEN emp_cv FOR\n" + + " SELECT last_name, salary FROM employees\n" + + " WHERE job_id = 'SA_REP'\n" + + " ORDER BY salary DESC;\n" + + "\n" + + " FETCH emp_cv BULK COLLECT INTO names, sals;\n" + + " CLOSE emp_cv;\n" + + " -- loop through the names and sals collections\n" + + " FOR i IN names.FIRST .. names.LAST\n" + + " LOOP\n" + + " DBMS_OUTPUT.PUT_LINE\n" + + " ('Name = ' || names(i) || ', salary = ' || sals(i));\n" + + " END LOOP;\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" + + "\tempcurtyp REF CURSOR;\n" + + "\temployees.last_name employees.last_name%TYPE;\n" + + "\temployees.salary employees.salary%TYPE;\n" + + "\temp_cv empcurtyp;\n" + + "\tnames namelist;\n" + + "\tsals sallist;\n" + + "BEGIN\n" + + "\tOPEN emp_cv FOR \n" + + "\t\tSELECT last_name, salary\n" + + "\t\tFROM employees\n" + + "\t\tWHERE job_id = 'SA_REP'\n" + + "\t\tORDER BY salary DESC;\n" + + "\tFETCH emp_cv BULK COLLECT INTO names, sals;\n" + + "\tCLOSE emp_cv;\n" + + "\tFOR i IN names.FIRST..names.LAST\n" + + "\tLOOP\n" + + "\t\tDBMS_OUTPUT.PUT_LINE('Name = ' || names(i) || ', salary = ' || sals(i));\n" + + "\tEND LOOP;\n" + + "END", // + output); + } + { + String output = SQLUtils.toOracleString(stmt, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION); + assertEquals("declare\n" + + "\tempcurtyp REF CURSOR;\n" + + "\temployees.last_name employees.last_name%TYPE;\n" + + "\temployees.salary employees.salary%TYPE;\n" + + "\temp_cv empcurtyp;\n" + + "\tnames namelist;\n" + + "\tsals sallist;\n" + + "begin\n" + + "\topen emp_cvfor \n" + + "\t\tselect last_name, salary\n" + + "\t\tfrom employees\n" + + "\t\twhere job_id = 'SA_REP'\n" + + "\t\torder by salary desc;\n" + + "\tfetch emp_cv bulk collect into names, sals;\n" + + "\tclose emp_cv;\n" + + "\tfor i in names.FIRST..names.LAST\n" + + "\tloop\n" + + "\t\tDBMS_OUTPUT.PUT_LINE('Name = ' || names(i) || ', salary = ' || sals(i));\n" + + "\tend loop;\n" + + "end", // + output); + } + } +}