diff --git a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLCaseStatement.java b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLCaseStatement.java new file mode 100644 index 0000000000..06a39fcac5 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLCaseStatement.java @@ -0,0 +1,160 @@ +/* + * 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.sql.ast.expr; + +import com.alibaba.druid.sql.ast.*; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class SQLCaseStatement extends SQLStatementImpl implements Serializable { + private final List items = new ArrayList(); + private SQLExpr valueExpr; + private List elseStatements = new ArrayList(); + + public SQLCaseStatement(){ + + } + + public SQLExpr getValueExpr() { + return this.valueExpr; + } + + public void setValueExpr(SQLExpr valueExpr) { + if (valueExpr != null) { + valueExpr.setParent(this); + } + this.valueExpr = valueExpr; + } + + public List getElseStatements() { + return elseStatements; + } + + public List getItems() { + return this.items; + } + + public void addItem(Item item) { + if (item != null) { + item.setParent(this); + this.items.add(item); + } + } + + protected void accept0(SQLASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, this.valueExpr); + acceptChild(visitor, this.items); + acceptChild(visitor, this.elseStatements); + } + visitor.endVisit(this); + } + + public static class Item extends SQLObjectImpl implements Serializable { + + private static final long serialVersionUID = 1L; + private SQLExpr conditionExpr; + private SQLStatement statement; + + public Item(){ + + } + + public Item(SQLExpr conditionExpr, SQLStatement statement){ + + setConditionExpr(conditionExpr); + setStatement(statement); + } + + public SQLExpr getConditionExpr() { + return this.conditionExpr; + } + + public void setConditionExpr(SQLExpr conditionExpr) { + if (conditionExpr != null) { + conditionExpr.setParent(this); + } + this.conditionExpr = conditionExpr; + } + + public SQLStatement getStatement() { + return this.statement; + } + + public void setStatement(SQLStatement statement) { + if (statement != null) { + statement.setParent(this); + } + this.statement = statement; + } + + protected void accept0(SQLASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, this.conditionExpr); + acceptChild(visitor, this.statement); + } + visitor.endVisit(this); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((conditionExpr == null) ? 0 : conditionExpr.hashCode()); + result = prime * result + ((statement == null) ? 0 : statement.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Item other = (Item) obj; + if (conditionExpr == null) { + if (other.conditionExpr != null) return false; + } else if (!conditionExpr.equals(other.conditionExpr)) return false; + if (statement == null) { + if (other.statement != null) return false; + } else if (!statement.equals(other.statement)) return false; + return true; + } + + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SQLCaseStatement that = (SQLCaseStatement) o; + + if (items != null ? !items.equals(that.items) : that.items != null) return false; + if (valueExpr != null ? !valueExpr.equals(that.valueExpr) : that.valueExpr != null) return false; + return elseStatements != null ? elseStatements.equals(that.elseStatements) : that.elseStatements == null; + } + + @Override + public int hashCode() { + int result = items != null ? items.hashCode() : 0; + result = 31 * result + (valueExpr != null ? valueExpr.hashCode() : 0); + result = 31 * result + (elseStatements != null ? elseStatements.hashCode() : 0); + return result; + } +} 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 a5e0efaf8d..aafdbc9e81 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 @@ -24,11 +24,7 @@ import com.alibaba.druid.sql.ast.SQLHint; import com.alibaba.druid.sql.ast.SQLName; import com.alibaba.druid.sql.ast.SQLParameter; import com.alibaba.druid.sql.ast.SQLStatement; -import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; -import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; -import com.alibaba.druid.sql.ast.expr.SQLCharExpr; -import com.alibaba.druid.sql.ast.expr.SQLNullExpr; -import com.alibaba.druid.sql.ast.expr.SQLQueryExpr; +import com.alibaba.druid.sql.ast.expr.*; import com.alibaba.druid.sql.ast.statement.*; import com.alibaba.druid.sql.dialect.oracle.ast.clause.OracleReturningClause; import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleAlterIndexStatement; @@ -459,6 +455,12 @@ public class OracleStatementParser extends SQLStatementParser { continue; } + if (lexer.token() == Token.CASE) { + SQLStatement stmt = this.parseCase(); + statementList.add(stmt); + continue; + } + if (lexer.token() == Token.ELSIF && parent instanceof SQLIfStatement) { break; } @@ -467,6 +469,45 @@ public class OracleStatementParser extends SQLStatementParser { } } + public SQLStatement parseCase() { + SQLCaseStatement caseStmt = new SQLCaseStatement(); + lexer.nextToken(); + if (lexer.token() != Token.WHEN) { + caseStmt.setValueExpr(this.exprParser.expr()); + } + + accept(Token.WHEN); + SQLExpr testExpr = this.exprParser.expr(); + accept(Token.THEN); + SQLStatement stmt = this.parseStatement(); + if (lexer.token() == Token.SEMI) { + lexer.nextToken(); + } + SQLCaseStatement.Item caseItem = new SQLCaseStatement.Item(testExpr, stmt); + caseStmt.addItem(caseItem); + + while (lexer.token() == Token.WHEN) { + lexer.nextToken(); + testExpr = this.exprParser.expr(); + accept(Token.THEN); + stmt = this.parseStatement(); + if (lexer.token() == Token.SEMI) { + lexer.nextToken(); + } + caseItem = new SQLCaseStatement.Item(testExpr, stmt); + caseStmt.addItem(caseItem); + } + + if (lexer.token() == Token.ELSE) { + lexer.nextToken(); + this.parseStatementList(caseStmt.getElseStatements(), -1, caseStmt); + } + + accept(Token.END); + accept(Token.CASE); + return caseStmt; + } + public SQLStatement parseIf() { accept(Token.IF); 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 9fe688b4b4..ca02d7958d 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -496,6 +496,43 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Paramet return false; } + public boolean visit(SQLCaseStatement x) { + print0(ucase ? "CASE" : "case"); + if (x.getValueExpr() != null) { + print(' '); + x.getValueExpr().accept(this); + } + incrementIndent(); + println(); + printlnAndAccept(x.getItems(), " "); + + if (x.getElseStatements() != null) { + println(); + print0(ucase ? "ELSE " : "else "); + printlnAndAccept(x.getElseStatements(), ";"); + print(';'); + } + + decrementIndent(); + + println(); + print0(ucase ? "END CASE" : "end case"); + return false; + } + + public boolean visit(SQLCaseStatement.Item x) { + print0(ucase ? "WHEN " : "when "); + x.getConditionExpr().accept(this); + print0(ucase ? " THEN " : " then "); + + SQLStatement stmt = x.getStatement(); + if (stmt != null) { + stmt.accept(this); + print(';'); + } + return false; + } + public boolean visit(SQLCastExpr x) { print0(ucase ? "CAST(" : "cast("); x.getExpr().accept(this); diff --git a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java index 6485b63b38..f8b8f444ff 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java @@ -31,6 +31,10 @@ public interface SQLASTVisitor { void endVisit(SQLCaseExpr.Item x); + void endVisit(SQLCaseStatement x); + + void endVisit(SQLCaseStatement.Item x); + void endVisit(SQLCharExpr x); void endVisit(SQLIdentifierExpr x); @@ -71,6 +75,10 @@ public interface SQLASTVisitor { boolean visit(SQLCaseExpr.Item x); + boolean visit(SQLCaseStatement x); + + boolean visit(SQLCaseStatement.Item x); + boolean visit(SQLCastExpr x); boolean visit(SQLCharExpr x); diff --git a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java index 735703fc19..b868f45362 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java @@ -39,6 +39,12 @@ public class SQLASTVisitorAdapter implements SQLASTVisitor { public void endVisit(SQLCaseExpr.Item x) { } + public void endVisit(SQLCaseStatement x) { + } + + public void endVisit(SQLCaseStatement.Item x) { + } + public void endVisit(SQLCharExpr x) { } @@ -104,6 +110,14 @@ public class SQLASTVisitorAdapter implements SQLASTVisitor { return true; } + public boolean visit(SQLCaseStatement x) { + return true; + } + + public boolean visit(SQLCaseStatement.Item x) { + return true; + } + public boolean visit(SQLCastExpr x) { return true; } diff --git a/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_case_0.java b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_case_0.java new file mode 100644 index 0000000000..e3916a981b --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/oracle/pl/Oracle_pl_case_0.java @@ -0,0 +1,106 @@ +/* + * 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_case_0 extends OracleTest { + + public void test_0() throws Exception { + String sql = "DECLARE\n" + + " grade CHAR(1);\n" + + "BEGIN\n" + + " grade := 'B';\n" + + " \n" + + " CASE\n" + + " WHEN grade = 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent');\n" + + " WHEN grade = 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good');\n" + + " WHEN grade = 'C' THEN DBMS_OUTPUT.PUT_LINE('Good');\n" + + " WHEN grade = 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair');\n" + + " WHEN grade = 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor');\n" + + " ELSE DBMS_OUTPUT.PUT_LINE('No such grade');\n" + + " END CASE;\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" + + "\tgrade CHAR(1);\n" + + "BEGIN\n" + + "\tgrade := 'B';\n" + + "\tCASE\n" + + "\t\tWHEN grade = 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent'); \n" + + "\t\tWHEN grade = 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good'); \n" + + "\t\tWHEN grade = 'C' THEN DBMS_OUTPUT.PUT_LINE('Good'); \n" + + "\t\tWHEN grade = 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair'); \n" + + "\t\tWHEN grade = 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor');\n" + + "\t\tELSE DBMS_OUTPUT.PUT_LINE('No such grade');\n" + + "\tEND CASE;\n" + + "END", // + output); + } + { + String output = SQLUtils.toOracleString(stmt, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION); + assertEquals("declare\n" + + "\tgrade CHAR(1);\n" + + "begin\n" + + "\tgrade := 'B';\n" + + "\tcase\n" + + "\t\twhen grade = 'A' then DBMS_OUTPUT.PUT_LINE('Excellent'); \n" + + "\t\twhen grade = 'B' then DBMS_OUTPUT.PUT_LINE('Very Good'); \n" + + "\t\twhen grade = 'C' then DBMS_OUTPUT.PUT_LINE('Good'); \n" + + "\t\twhen grade = 'D' then DBMS_OUTPUT.PUT_LINE('Fair'); \n" + + "\t\twhen grade = 'F' then DBMS_OUTPUT.PUT_LINE('Poor');\n" + + "\t\telse DBMS_OUTPUT.PUT_LINE('No such grade');\n" + + "\tend case;\n" + + "end", // + output); + } + } +}