From 68c5630cd1949476089ec1c359d681281c9cdea2 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Mon, 6 Jul 2015 00:51:09 +0800 Subject: [PATCH] improved sql parser support CreateView --- .../ast/statement/SQLCreateViewStatement.java | 80 ++++++++++++++++--- .../druid/sql/parser/SQLStatementParser.java | 41 +++++++++- .../sql/visitor/SQLASTOutputVisitor.java | 57 ++++++++++--- .../druid/sql/visitor/SQLASTVisitor.java | 4 + .../sql/visitor/SQLASTVisitorAdapter.java | 7 ++ .../bvt/sql/odps/OdpsCreateViewTest.java | 79 ++++++++++++++++++ 6 files changed, 247 insertions(+), 21 deletions(-) create mode 100644 src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsCreateViewTest.java diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLCreateViewStatement.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLCreateViewStatement.java index 4f4a345754..8828d1ca6a 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLCreateViewStatement.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLCreateViewStatement.java @@ -20,25 +20,31 @@ import java.util.List; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLName; +import com.alibaba.druid.sql.ast.SQLObjectImpl; import com.alibaba.druid.sql.ast.SQLStatementImpl; +import com.alibaba.druid.sql.ast.expr.SQLCharExpr; +import com.alibaba.druid.sql.ast.expr.SQLLiteralExpr; import com.alibaba.druid.sql.visitor.SQLASTVisitor; public class SQLCreateViewStatement extends SQLStatementImpl implements SQLDDLStatement { - private boolean orReplace = false; - protected SQLName name; - protected SQLSelect subQuery; + private boolean orReplace = false; + protected SQLName name; + protected SQLSelect subQuery; + protected boolean ifNotExists = false; - protected final List columns = new ArrayList(); + protected final List columns = new ArrayList(); - private Level with; + private Level with; + + private SQLLiteralExpr comment; public SQLCreateViewStatement(){ } - + public SQLCreateViewStatement(String dbType){ - super (dbType); + super(dbType); } public boolean isOrReplace() { @@ -73,10 +79,29 @@ public class SQLCreateViewStatement extends SQLStatementImpl implements SQLDDLSt this.subQuery = subQuery; } - public List getColumns() { + public List getColumns() { return columns; } + public boolean isIfNotExists() { + return ifNotExists; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public SQLLiteralExpr getComment() { + return comment; + } + + public void setComment(SQLLiteralExpr comment) { + if (comment != null) { + comment.setParent(this); + } + this.comment = comment; + } + public void output(StringBuffer buf) { buf.append("CREATE VIEW "); this.name.output(buf); @@ -106,12 +131,49 @@ public class SQLCreateViewStatement extends SQLStatementImpl implements SQLDDLSt if (visitor.visit(this)) { acceptChild(visitor, this.name); acceptChild(visitor, this.columns); + acceptChild(visitor, this.comment); acceptChild(visitor, this.subQuery); } visitor.endVisit(this); } public static enum Level { - CASCADED, LOCAL + CASCADED, LOCAL + } + + public static class Column extends SQLObjectImpl { + + private SQLExpr expr; + private SQLCharExpr comment; + + public SQLExpr getExpr() { + return expr; + } + + public void setExpr(SQLExpr expr) { + if (expr != null) { + expr.setParent(this); + } + this.expr = expr; + } + + public SQLCharExpr getComment() { + return comment; + } + + public void setComment(SQLCharExpr comment) { + if (comment != null) { + comment.setParent(this); + } + this.comment = comment; + } + + @Override + protected void accept0(SQLASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, expr); + acceptChild(visitor, comment); + } + } } } 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 fdd2a3df6b..7ecf1eb47a 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java @@ -22,6 +22,7 @@ import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLName; 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; @@ -1344,6 +1345,11 @@ public class SQLStatementParser extends SQLParser { lexer.reset(markBp, markChar, Token.CREATE); return parseCreateProcedure(); } + + if (lexer.token() == Token.VIEW) { + lexer.reset(markBp, markChar, Token.CREATE); + return parseCreateView(); + } // lexer.reset(mark_bp, mark_ch, Token.CREATE); throw new ParserException("TODO " + lexer.token() + " " + lexer.stringVal()); @@ -1600,14 +1606,47 @@ public class SQLStatementParser extends SQLParser { } this.accept(Token.VIEW); + + if (lexer.token() == Token.IF || identifierEquals("IF")) { + lexer.nextToken(); + accept(Token.NOT); + accept(Token.EXISTS); + createView.setIfNotExists(true); + } createView.setName(exprParser.name()); if (lexer.token() == Token.LPAREN) { lexer.nextToken(); - this.exprParser.exprList(createView.getColumns(), createView); + + for (;;) { + SQLCreateViewStatement.Column column = new SQLCreateViewStatement.Column(); + SQLExpr expr = this.exprParser.expr(); + column.setExpr(expr); + + if (lexer.token() == Token.COMMENT) { + lexer.nextToken(); + column.setComment((SQLCharExpr) exprParser.primary()); + } + + column.setParent(createView); + createView.getColumns().add(column); + + if (lexer.token() == Token.COMMA) { + lexer.nextToken(); + } else { + break; + } + } + accept(Token.RPAREN); } + + if (lexer.token() == Token.COMMENT) { + lexer.nextToken(); + SQLCharExpr comment = (SQLCharExpr) exprParser.primary(); + createView.setComment(comment); + } this.accept(Token.AS); 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 a6e0720405..3644c6bf4b 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -156,7 +156,7 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab private boolean prettyFormat = true; protected int selectListNumberOfLine = 5; - private List parameters; + private List parameters; public SQLASTOutputVisitor(Appendable appender){ this.appender = appender; @@ -726,7 +726,7 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab if (alias != null && alias.length() > 0) { print(" AS "); if (alias.indexOf(' ') == -1 || alias.charAt(0) == '"' || alias.charAt(0) == '\'') { - print(alias); + print(alias); } else { print('"'); print(alias); @@ -1070,7 +1070,7 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab println(); print(")"); } - + if (x.getInherits() != null) { print(" INHERITS ("); x.getInherits().accept(this); @@ -1411,7 +1411,7 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab public boolean visit(SQLAlterTableDropColumnItem x) { print("DROP COLUMN "); this.printAndAccept(x.getColumns(), ", "); - + if (x.isCascade()) { print(" CASCADE"); } @@ -1481,20 +1481,55 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab print("OR REPLACE "); } print("VIEW "); + + if (x.isIfNotExists()) { + print("IF NOT EXISTS "); + } + x.getName().accept(this); if (x.getColumns().size() > 0) { - print(" ("); - printAndAccept(x.getColumns(), ", "); + println(); + print("("); + incrementIndent(); + println(); + for (int i = 0; i < x.getColumns().size(); ++i) { + if (i != 0) { + print(", "); + println(); + } + x.getColumns().get(i).accept(this); + } + decrementIndent(); + println(); print(")"); } - print(" AS "); + if (x.getComment() != null) { + println(); + print("COMMENT "); + x.getComment().accept(this); + } + + println(); + print("AS"); + println(); x.getSubQuery().accept(this); return false; } + public boolean visit(SQLCreateViewStatement.Column x) { + x.getExpr().accept(this); + + if (x.getComment() != null) { + print(" COMMENT "); + x.getComment().accept(this); + } + + return false; + } + @Override public boolean visit(SQLAlterTableDropIndex x) { print("DROP INDEX "); @@ -1600,7 +1635,7 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab public boolean visit(SQLAlterTableAlterColumn x) { print("ALTER COLUMN "); x.getColumn().accept(this); - + if (x.isSetNotNull()) { // postgresql print(" SET NOT NULL"); } @@ -1614,7 +1649,7 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab if (x.isDropDefault()) { // postgresql print(" DROP DEFAULT"); } - + return false; } @@ -2115,7 +2150,7 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab return false; } - + @Override public boolean visit(SQLBinaryExpr x) { print("b'"); @@ -2124,7 +2159,7 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab return false; } - + @Override public boolean visit(SQLAlterTableRename x) { print("RENAME TO "); 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 6063f3d081..0a15b8f3ef 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java @@ -292,6 +292,10 @@ public interface SQLASTVisitor { boolean visit(SQLCreateViewStatement x); void endVisit(SQLCreateViewStatement x); + + boolean visit(SQLCreateViewStatement.Column x); + + void endVisit(SQLCreateViewStatement.Column x); boolean visit(NotNullConstraint 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 1eb4cb9532..c6741084c1 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java @@ -391,6 +391,13 @@ public class SQLASTVisitorAdapter implements SQLASTVisitor { public void endVisit(SQLCreateViewStatement x) { } + + public boolean visit(SQLCreateViewStatement.Column x) { + return true; + } + + public void endVisit(SQLCreateViewStatement.Column x) { + } public boolean visit(NotNullConstraint x) { return true; diff --git a/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsCreateViewTest.java b/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsCreateViewTest.java new file mode 100644 index 0000000000..3cadaa97a1 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsCreateViewTest.java @@ -0,0 +1,79 @@ +package com.alibaba.druid.bvt.sql.odps; + +import org.junit.Assert; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.odps.parser.OdpsStatementParser; +import com.alibaba.druid.sql.parser.Token; + +import junit.framework.TestCase; + +public class OdpsCreateViewTest extends TestCase { + + public void test_create() throws Exception { + String sql = "CREATE view sale_detail as select * from dual;"; + OdpsStatementParser parser = new OdpsStatementParser(sql); + SQLStatement stmt = parser.parseStatementList().get(0); + parser.match(Token.EOF); + String output = SQLUtils.toOdpsString(stmt); + Assert.assertEquals("CREATE VIEW sale_detail" // + + "\nAS" // + + "\nSELECT *" // + + "\nFROM dual", output); + } + + public void test_create_or_replace() throws Exception { + String sql = "CREATE or replace view sale_detail as select * from dual;"; + OdpsStatementParser parser = new OdpsStatementParser(sql); + SQLStatement stmt = parser.parseStatementList().get(0); + parser.match(Token.EOF); + String output = SQLUtils.toOdpsString(stmt); + Assert.assertEquals("CREATE OR REPLACE VIEW sale_detail" // + + "\nAS" // + + "\nSELECT *" // + + "\nFROM dual", output); + } + + + public void test_create_if_not_exists() throws Exception { + String sql = "CREATE view if not exists sale_detail as select * from dual;"; + OdpsStatementParser parser = new OdpsStatementParser(sql); + SQLStatement stmt = parser.parseStatementList().get(0); + parser.match(Token.EOF); + String output = SQLUtils.toOdpsString(stmt); + Assert.assertEquals("CREATE VIEW IF NOT EXISTS sale_detail" // + + "\nAS" // + + "\nSELECT *" // + + "\nFROM dual", output); + } + + public void test_create_comments() throws Exception { + String sql = "CREATE view if not exists sale_detail comment 'aaaa' as select * from dual;"; + OdpsStatementParser parser = new OdpsStatementParser(sql); + SQLStatement stmt = parser.parseStatementList().get(0); + parser.match(Token.EOF); + String output = SQLUtils.toOdpsString(stmt); + Assert.assertEquals("CREATE VIEW IF NOT EXISTS sale_detail" // + + "\nCOMMENT 'aaaa'" // + + "\nAS" // + + "\nSELECT *" // + + "\nFROM dual", output); + } + + public void test_create_column_comments() throws Exception { + String sql = "CREATE view if not exists sale_detail (f1 comment 'aaaa', f2 comment 'bbb') as select * from dual;"; + OdpsStatementParser parser = new OdpsStatementParser(sql); + SQLStatement stmt = parser.parseStatementList().get(0); + parser.match(Token.EOF); + String output = SQLUtils.toOdpsString(stmt); + Assert.assertEquals("CREATE VIEW IF NOT EXISTS sale_detail" // + + "\n(" // + + "\n\tf1 COMMENT 'aaaa', " // + + "\n\tf2 COMMENT 'bbb'" // + + "\n)" // + + "\nAS" // + + "\nSELECT *" // + + "\nFROM dual", output); + } +}