diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLExplainStatement.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLExplainStatement.java new file mode 100644 index 0000000000..b0e839cc46 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLExplainStatement.java @@ -0,0 +1,29 @@ +package com.alibaba.druid.sql.ast.statement; + +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.SQLStatementImpl; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +public class SQLExplainStatement extends SQLStatementImpl { + + protected SQLStatement statement; + + public SQLStatement getStatement() { + return statement; + } + + public void setStatement(SQLStatement statement) { + if (statement != null) { + statement.setParent(this); + } + this.statement = statement; + } + + @Override + protected void accept0(SQLASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, statement); + } + visitor.endVisit(this); + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlOutputVisitor.java index f466e9dad9..bbde4490d9 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlOutputVisitor.java @@ -35,7 +35,6 @@ import com.alibaba.druid.sql.ast.statement.SQLCharactorDataType; import com.alibaba.druid.sql.ast.statement.SQLColumnConstraint; import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition; import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement; -import com.alibaba.druid.sql.ast.statement.SQLDropUserStatement; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; import com.alibaba.druid.sql.dialect.mysql.ast.MySqlForceIndexHint; diff --git a/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/stmt/OracleExplainStatement.java b/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/stmt/OracleExplainStatement.java index ba4dbbd78d..4555c8588f 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/stmt/OracleExplainStatement.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/stmt/OracleExplainStatement.java @@ -15,27 +15,36 @@ */ package com.alibaba.druid.sql.dialect.oracle.ast.stmt; +import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLExpr; -import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.expr.SQLCharExpr; +import com.alibaba.druid.sql.ast.statement.SQLExplainStatement; import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitor; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; -public class OracleExplainStatement extends OracleStatementImpl { +public class OracleExplainStatement extends SQLExplainStatement implements OracleStatement { - private SQLCharExpr statementId; - private SQLExpr into; - private SQLStatement forStatement; + private SQLCharExpr statementId; + private SQLExpr into; @Override public void accept0(OracleASTVisitor visitor) { if (visitor.visit(this)) { acceptChild(visitor, statementId); acceptChild(visitor, into); - acceptChild(visitor, forStatement); + acceptChild(visitor, statement); } visitor.endVisit(this); } + protected void accept0(SQLASTVisitor visitor) { + accept0((OracleASTVisitor) visitor); + } + + public String toString() { + return SQLUtils.toOracleString(this); + } + public SQLCharExpr getStatementId() { return statementId; } @@ -52,12 +61,4 @@ public class OracleExplainStatement extends OracleStatementImpl { this.into = into; } - public SQLStatement getForStatement() { - return forStatement; - } - - public void setForStatement(SQLStatement forStatement) { - this.forStatement = forStatement; - } - } 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 730575e789..17fb4806b5 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 @@ -140,11 +140,6 @@ public class OracleStatementParser extends SQLStatementParser { } } - @Override - public void parseStatementList(List statementList) { - parseStatementList(statementList, -1); - } - public void parseStatementList(List statementList, int max) { for (;;) { if (max != -1) { @@ -298,12 +293,12 @@ public class OracleStatementParser extends SQLStatementParser { continue; } - if (lexer.token() == Token.IDENTIFIER) { - if (identifierEquals("EXPLAIN")) { - statementList.add(this.parseExplain()); - continue; - } + if (lexer.token() == Token.EXPLAIN) { + statementList.add(this.parseExplain()); + continue; + } + if (lexer.token() == Token.IDENTIFIER) { SQLExpr expr = exprParser.expr(); OracleExprStatement stmt = new OracleExprStatement(expr); statementList.add(stmt); @@ -470,7 +465,7 @@ public class OracleStatementParser extends SQLStatementParser { statementList.add(stmt); continue; } - + if (lexer.token() == Token.TRIGGER) { SQLDropTriggerStatement stmt = parseDropTrigger(false); statementList.add(stmt); @@ -1453,16 +1448,8 @@ public class OracleStatementParser extends SQLStatementParser { return clause; } - public SQLStatement parseStatement() { - List list = new ArrayList(); - - this.parseStatementList(list, 1); - - return list.get(0); - } - public OracleExplainStatement parseExplain() { - acceptIdentifier("EXPLAIN"); + accept(Token.EXPLAIN); acceptIdentifier("PLAN"); OracleExplainStatement stmt = new OracleExplainStatement(); @@ -1479,7 +1466,7 @@ public class OracleStatementParser extends SQLStatementParser { } accept(Token.FOR); - stmt.setForStatement(parseStatement()); + stmt.setStatement(parseStatement()); return stmt; } 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 3da0da89d9..70173b4626 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 @@ -1882,7 +1882,7 @@ public class OracleOutputVisitor extends SQLASTOutputVisitor implements OracleAS print("FRO"); println(); - x.getForStatement().accept(this); + x.getStatement().accept(this); decrementIndent(); return false; diff --git a/src/main/java/com/alibaba/druid/sql/parser/Keywords.java b/src/main/java/com/alibaba/druid/sql/parser/Keywords.java index d1458b5836..cdfe6e4981 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/Keywords.java +++ b/src/main/java/com/alibaba/druid/sql/parser/Keywords.java @@ -58,6 +58,7 @@ public class Keywords { map.put("DISTINCT", Token.DISTINCT); map.put("DROP", Token.DROP); map.put("ELSE", Token.ELSE); + map.put("EXPLAIN", Token.EXPLAIN); map.put("END", Token.END); map.put("ESCAPE", Token.ESCAPE); diff --git a/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java b/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java index 27c18935a1..417e43b702 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java @@ -245,6 +245,7 @@ public class SQLExprParser extends SQLParser { case INITIALLY: case SEQUENCE: case USER: + case EXPLAIN: sqlExpr = new SQLIdentifierExpr(lexer.stringVal()); lexer.nextToken(); break; 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 7df58bed06..d4dc2f9a18 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java @@ -48,6 +48,7 @@ import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement; import com.alibaba.druid.sql.ast.statement.SQLDropTriggerStatement; import com.alibaba.druid.sql.ast.statement.SQLDropUserStatement; import com.alibaba.druid.sql.ast.statement.SQLDropViewStatement; +import com.alibaba.druid.sql.ast.statement.SQLExplainStatement; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLInsertInto; import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; @@ -89,7 +90,17 @@ public class SQLStatementParser extends SQLParser { } public void parseStatementList(List statementList) { + parseStatementList(statementList, -1); + } + + public void parseStatementList(List statementList, int max) { for (;;) { + if (max != -1) { + if (statementList.size() >= max) { + return; + } + } + if (lexer.token() == Token.EOF) { return; } @@ -125,6 +136,11 @@ public class SQLStatementParser extends SQLParser { statementList.add(parseDeleteStatement()); continue; } + + if (lexer.token() == (Token.EXPLAIN)) { + statementList.add(parseExplain()); + continue; + } if (lexer.token() == Token.SET) { statementList.add(parseSet()); @@ -587,7 +603,7 @@ public class SQLStatementParser extends SQLParser { stmt.setCascade(Boolean.FALSE); continue; } - + break; } @@ -1000,4 +1016,28 @@ public class SQLStatementParser extends SQLParser { } return item; } + + public SQLStatement parseStatement() { + List list = new ArrayList(); + + this.parseStatementList(list, 1); + + return list.get(0); + } + + public SQLExplainStatement parseExplain() { + accept(Token.EXPLAIN); + if (identifierEquals("PLAN")) { + lexer.nextToken(); + } + + if (lexer.token() == Token.FOR) { + lexer.nextToken(); + } + + SQLExplainStatement explain = new SQLExplainStatement(); + explain.setStatement(parseStatement()); + + return explain; + } } diff --git a/src/main/java/com/alibaba/druid/sql/parser/Token.java b/src/main/java/com/alibaba/druid/sql/parser/Token.java index d68349ec39..9145b2c493 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/Token.java +++ b/src/main/java/com/alibaba/druid/sql/parser/Token.java @@ -22,6 +22,7 @@ package com.alibaba.druid.sql.parser; * @formatter:off */ public enum Token { + EXPLAIN("EXPLAIN"), FOR("FOR"), IF("IF"), INDEX("INDEX"), 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 0c3744e251..77f7dcddec 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -101,6 +101,7 @@ import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement; import com.alibaba.druid.sql.ast.statement.SQLDropTriggerStatement; import com.alibaba.druid.sql.ast.statement.SQLDropUserStatement; import com.alibaba.druid.sql.ast.statement.SQLDropViewStatement; +import com.alibaba.druid.sql.ast.statement.SQLExplainStatement; import com.alibaba.druid.sql.ast.statement.SQLExprHint; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLForeignKeyConstraint; @@ -1705,4 +1706,12 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab printAndAccept(x.getUsers(), ", "); return false; } + + @Override + public boolean visit(SQLExplainStatement x) { + print("EXPLAIN"); + println(); + x.getStatement().accept(this); + return false; + } } 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 50048c293b..fe021cdeaf 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java @@ -85,6 +85,7 @@ import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement; import com.alibaba.druid.sql.ast.statement.SQLDropTriggerStatement; import com.alibaba.druid.sql.ast.statement.SQLDropUserStatement; import com.alibaba.druid.sql.ast.statement.SQLDropViewStatement; +import com.alibaba.druid.sql.ast.statement.SQLExplainStatement; import com.alibaba.druid.sql.ast.statement.SQLExprHint; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLForeignKeyImpl; @@ -499,4 +500,8 @@ public interface SQLASTVisitor { void endVisit(SQLDropUserStatement x); boolean visit(SQLDropUserStatement x); + + void endVisit(SQLExplainStatement x); + + boolean visit(SQLExplainStatement 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 193baf73aa..21a6fe1b71 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java @@ -86,6 +86,7 @@ import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement; import com.alibaba.druid.sql.ast.statement.SQLDropTriggerStatement; import com.alibaba.druid.sql.ast.statement.SQLDropUserStatement; import com.alibaba.druid.sql.ast.statement.SQLDropViewStatement; +import com.alibaba.druid.sql.ast.statement.SQLExplainStatement; import com.alibaba.druid.sql.ast.statement.SQLExprHint; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLForeignKeyImpl; @@ -964,4 +965,14 @@ public class SQLASTVisitorAdapter implements SQLASTVisitor { public boolean visit(SQLDropUserStatement x) { return true; } + + @Override + public void endVisit(SQLExplainStatement x) { + + } + + @Override + public boolean visit(SQLExplainStatement x) { + return true; + } } diff --git a/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlDescTest_0.java b/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlDescTest_0.java new file mode 100644 index 0000000000..809c7be338 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlDescTest_0.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2011 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.mysql; + +import java.util.List; + +import org.junit.Assert; + +import com.alibaba.druid.sql.MysqlTest; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; +import com.alibaba.druid.stat.TableStat; + +public class MySqlDescTest_0 extends MysqlTest { + + public void test_0() throws Exception { + String sql = " Desc City"; + + MySqlStatementParser parser = new MySqlStatementParser(sql); + List statementList = parser.parseStatementList(); + SQLStatement stmt = statementList.get(0); + print(statementList); + + Assert.assertEquals(1, statementList.size()); + + MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); + stmt.accept(visitor); + + System.out.println("Tables : " + visitor.getTables()); + System.out.println("fields : " + visitor.getColumns()); + System.out.println("coditions : " + visitor.getConditions()); + System.out.println("orderBy : " + visitor.getOrderByColumns()); + + Assert.assertEquals(1, visitor.getTables().size()); + Assert.assertEquals(0, visitor.getColumns().size()); + Assert.assertEquals(0, visitor.getConditions().size()); + + Assert.assertTrue(visitor.getTables().containsKey(new TableStat.Name("City"))); +// Assert.assertTrue(visitor.getTables().containsKey(new TableStat.Name("t2"))); + +// Assert.assertTrue(visitor.getColumns().contains(new Column("t2", "id"))); + } +} diff --git a/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlDescribteTest.java b/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlDescribteTest.java index 0adc36c293..80832dd6ca 100644 --- a/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlDescribteTest.java +++ b/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlDescribteTest.java @@ -32,13 +32,13 @@ public class MySqlDescribteTest extends MysqlTest { MySqlStatementParser parser = new MySqlStatementParser(sql); List statementList = parser.parseStatementList(); - SQLStatement statemen = statementList.get(0); + SQLStatement stmt = statementList.get(0); print(statementList); Assert.assertEquals(1, statementList.size()); MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); - statemen.accept(visitor); + stmt.accept(visitor); System.out.println("Tables : " + visitor.getTables()); System.out.println("fields : " + visitor.getColumns()); diff --git a/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlExplainTest_0.java b/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlExplainTest_0.java new file mode 100644 index 0000000000..691844caae --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlExplainTest_0.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2011 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.mysql; + +import java.util.List; + +import org.junit.Assert; + +import com.alibaba.druid.sql.MysqlTest; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; +import com.alibaba.druid.stat.TableStat; + +public class MySqlExplainTest_0 extends MysqlTest { + + public void test_0() throws Exception { + String sql = "explain SELECT * FROM sonar.groups where id = 1"; + + MySqlStatementParser parser = new MySqlStatementParser(sql); + List statementList = parser.parseStatementList(); + SQLStatement stmt = statementList.get(0); + print(statementList); + + Assert.assertEquals(1, statementList.size()); + + MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); + stmt.accept(visitor); + + System.out.println("Tables : " + visitor.getTables()); + System.out.println("fields : " + visitor.getColumns()); + System.out.println("coditions : " + visitor.getConditions()); + System.out.println("orderBy : " + visitor.getOrderByColumns()); + + Assert.assertEquals(1, visitor.getTables().size()); + Assert.assertEquals(2, visitor.getColumns().size()); + Assert.assertEquals(1, visitor.getConditions().size()); + + Assert.assertTrue(visitor.getTables().containsKey(new TableStat.Name("sonar.groups"))); +// Assert.assertTrue(visitor.getTables().containsKey(new TableStat.Name("t2"))); + +// Assert.assertTrue(visitor.getColumns().contains(new Column("t2", "id"))); + } +} diff --git a/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlSetTest_0.java b/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlSetTest_0.java index bfcea29eef..f3ffa583dc 100644 --- a/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlSetTest_0.java +++ b/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlSetTest_0.java @@ -30,14 +30,14 @@ public class MySqlSetTest_0 extends MysqlTest { String sql = "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"; MySqlStatementParser parser = new MySqlStatementParser(sql); - List stmt = parser.parseStatementList(); - SQLStatement statemen = stmt.get(0); - print(stmt); + List stmtList = parser.parseStatementList(); + SQLStatement stmt = stmtList.get(0); + print(stmtList); - Assert.assertEquals(1, stmt.size()); + Assert.assertEquals(1, stmtList.size()); MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); - statemen.accept(visitor); + stmt.accept(visitor); System.out.println("Tables : " + visitor.getTables()); System.out.println("fields : " + visitor.getColumns()); @@ -48,7 +48,7 @@ public class MySqlSetTest_0 extends MysqlTest { Assert.assertEquals(0, visitor.getColumns().size()); Assert.assertEquals(0, visitor.getConditions().size()); - String text = output(stmt); + String text = output(stmtList); Assert.assertEquals("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ", text); }