diff --git a/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleSelectParser.java b/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleSelectParser.java index 0a093a0589..77ae6f8368 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleSelectParser.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleSelectParser.java @@ -70,7 +70,8 @@ import com.alibaba.druid.sql.parser.Token; public class OracleSelectParser extends SQLSelectParser { public OracleSelectParser(String sql){ - super(sql); + this(new OracleLexer(sql)); + this.lexer.nextToken(); } public OracleSelectParser(Lexer lexer){ diff --git a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/SQLServerObjectImpl.java b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/SQLServerObjectImpl.java new file mode 100644 index 0000000000..b10cf572fe --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/SQLServerObjectImpl.java @@ -0,0 +1,36 @@ +/* + * 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.sql.dialect.sqlserver.ast; + +import com.alibaba.druid.sql.ast.SQLObjectImpl; +import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerASTVisitor; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +public abstract class SQLServerObjectImpl extends SQLObjectImpl implements SQLServerObject { + + private static final long serialVersionUID = 1L; + + public SQLServerObjectImpl(){ + + } + + @Override + protected void accept0(SQLASTVisitor visitor) { + this.accept0((SQLServerASTVisitor) visitor); + } + + public abstract void accept0(SQLServerASTVisitor visitor); +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/SQLServerSelectQueryBlock.java b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/SQLServerSelectQueryBlock.java new file mode 100644 index 0000000000..4a45be5f57 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/SQLServerSelectQueryBlock.java @@ -0,0 +1,39 @@ +package com.alibaba.druid.sql.dialect.sqlserver.ast; + +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerASTVisitor; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +public class SQLServerSelectQueryBlock extends SQLSelectQueryBlock { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private Top top; + + public Top getTop() { + return top; + } + + public void setTop(Top top) { + this.top = top; + } + + @Override + protected void accept0(SQLASTVisitor visitor) { + accept0((SQLServerASTVisitor) visitor); + } + + protected void accept0(SQLServerASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, this.top); + acceptChild(visitor, this.selectList); + acceptChild(visitor, this.from); + acceptChild(visitor, this.where); + acceptChild(visitor, this.groupBy); + } + visitor.endVisit(this); + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/Top.java b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/Top.java new file mode 100644 index 0000000000..4780ecd71a --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/Top.java @@ -0,0 +1,48 @@ +package com.alibaba.druid.sql.dialect.sqlserver.ast; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerASTVisitor; + +public class Top extends SQLServerObjectImpl { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private SQLExpr expr; + private boolean percent; + private boolean withTies; + + public SQLExpr getExpr() { + return expr; + } + + public void setExpr(SQLExpr expr) { + this.expr = expr; + } + + public boolean isPercent() { + return percent; + } + + public void setPercent(boolean percent) { + this.percent = percent; + } + + public boolean isWithTies() { + return withTies; + } + + public void setWithTies(boolean withTies) { + this.withTies = withTies; + } + + @Override + public void accept0(SQLServerASTVisitor visitor) { + if (visitor.visit(this)) { + + } + } + +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerLexer.java b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerLexer.java index 394a043919..edfa2da560 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerLexer.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerLexer.java @@ -86,6 +86,7 @@ public class SQLServerLexer extends Lexer { map.put("WITH", Token.WITH); map.put("CURSOR", Token.CURSOR); + map.put("TOP", Token.TOP); DEFAULT_SQL_SERVER_KEYWORDS = new Keywords(map); } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerSelectParser.java b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerSelectParser.java new file mode 100644 index 0000000000..787bc2a035 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerSelectParser.java @@ -0,0 +1,77 @@ +package com.alibaba.druid.sql.dialect.sqlserver.parser; + +import com.alibaba.druid.sql.ast.SQLSetQuantifier; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; +import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelect; +import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock; +import com.alibaba.druid.sql.dialect.sqlserver.ast.Top; +import com.alibaba.druid.sql.parser.Lexer; +import com.alibaba.druid.sql.parser.ParserException; +import com.alibaba.druid.sql.parser.SQLSelectParser; +import com.alibaba.druid.sql.parser.Token; + +public class SQLServerSelectParser extends SQLSelectParser { + + public SQLServerSelectParser(String sql){ + super(new SQLServerLexer(sql)); + this.lexer.nextToken(); + } + + public SQLServerSelectParser(Lexer lexer){ + super(lexer); + } + + public SQLSelect select() throws ParserException { + SQLServerSelect select = new SQLServerSelect(); + + select.setQuery(query()); + select.setOrderBy(parseOrderBy()); + + if (select.getOrderBy() == null) { + select.setOrderBy(parseOrderBy()); + } + + return select; + } + + protected SQLSelectQuery query() throws ParserException { + if (lexer.token() == Token.LPAREN) { + lexer.nextToken(); + + SQLSelectQuery select = query(); + accept(Token.RPAREN); + + return queryRest(select); + } + + accept(Token.SELECT); + + SQLServerSelectQueryBlock queryBlock = new SQLServerSelectQueryBlock(); + + if (lexer.token() == Token.DISTINCT) { + queryBlock.setDistionOption(SQLSetQuantifier.DISTINCT); + lexer.nextToken(); + } else if (lexer.token() == Token.ALL) { + queryBlock.setDistionOption(SQLSetQuantifier.ALL); + lexer.nextToken(); + } + + if (lexer.token() == Token.TOP) { + Top top = new Top(); + lexer.nextToken(); + top.setExpr(createExprParser().primary()); + queryBlock.setTop(top); + } + + parseSelectList(queryBlock); + + parseFrom(queryBlock); + + parseWhere(queryBlock); + + parseGroupBy(queryBlock); + + return queryRest(queryBlock); + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerStatementParser.java b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerStatementParser.java index 48cb09c5bc..c762bde07c 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerStatementParser.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/parser/SQLServerStatementParser.java @@ -25,7 +25,6 @@ import com.alibaba.druid.sql.dialect.oracle.parser.OracleInsertParser; import com.alibaba.druid.sql.dialect.oracle.parser.OracleSelectParser; import com.alibaba.druid.sql.parser.Lexer; import com.alibaba.druid.sql.parser.ParserException; -import com.alibaba.druid.sql.parser.SQLSelectParser; import com.alibaba.druid.sql.parser.SQLStatementParser; import com.alibaba.druid.sql.parser.Token; @@ -53,7 +52,7 @@ public class SQLServerStatementParser extends SQLStatementParser { } if (lexer.token() == (Token.SELECT)) { - statementList.add(new SQLSelectStatement(new SQLSelectParser(this.lexer).select())); + statementList.add(new SQLSelectStatement(new SQLServerSelectParser(this.lexer).select())); continue; } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/visitor/SQLServerASTVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/visitor/SQLServerASTVisitor.java new file mode 100644 index 0000000000..0d38b1ecad --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/visitor/SQLServerASTVisitor.java @@ -0,0 +1,15 @@ +package com.alibaba.druid.sql.dialect.sqlserver.visitor; + +import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock; +import com.alibaba.druid.sql.dialect.sqlserver.ast.Top; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +public interface SQLServerASTVisitor extends SQLASTVisitor { + boolean visit(SQLServerSelectQueryBlock x); + + void endVisit(SQLServerSelectQueryBlock x); + + boolean visit(Top x); + + void endVisit(Top x); +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/visitor/SqlServerOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/visitor/SqlServerOutputVisitor.java new file mode 100644 index 0000000000..223dbd4c4c --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/visitor/SqlServerOutputVisitor.java @@ -0,0 +1,69 @@ +package com.alibaba.druid.sql.dialect.sqlserver.visitor; + +import com.alibaba.druid.sql.ast.SQLSetQuantifier; +import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock; +import com.alibaba.druid.sql.dialect.sqlserver.ast.Top; +import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; + +public class SqlServerOutputVisitor extends SQLASTOutputVisitor implements SQLServerASTVisitor { + + public SqlServerOutputVisitor(Appendable appender){ + super(appender); + } + + public boolean visit(SQLServerSelectQueryBlock select) { + print("SELECT "); + + if (SQLSetQuantifier.ALL == select.getDistionOption()) { + print("ALL "); + } else if (SQLSetQuantifier.DISTINCT == select.getDistionOption()) { + print("DISTINCT "); + } else if (SQLSetQuantifier.UNIQUE == select.getDistionOption()) { + print("UNIQUE "); + } + + if (select.getTop() != null) { + select.getTop().accept(this); + } + + printSelectList(select.getSelectList()); + + if (select.getFrom() != null) { + println(); + print("FROM "); + select.getFrom().accept(this); + } + + if (select.getWhere() != null) { + println(); + print("WHERE "); + select.getWhere().accept(this); + } + + if (select.getGroupBy() != null) { + print(" "); + select.getGroupBy().accept(this); + } + + return false; + } + + @Override + public void endVisit(SQLServerSelectQueryBlock x) { + + } + + @Override + public boolean visit(Top x) { + print("TOP "); + x.getExpr().accept(this); + print(" "); + return false; + } + + @Override + public void endVisit(Top x) { + + } + +} 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 42c133d443..9ff5432ba0 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/Token.java +++ b/src/main/java/com/alibaba/druid/sql/parser/Token.java @@ -114,6 +114,9 @@ public enum Token { SECOND("SECOND"), TO("TO"), MODEL("MODEL"), + + // transact-sql + TOP("TOP"), EOF, ERROR, diff --git a/src/test/java/com/alibaba/druid/bvt/sql/sqlserver/SQLServerTopTest.java b/src/test/java/com/alibaba/druid/bvt/sql/sqlserver/SQLServerTopTest.java new file mode 100644 index 0000000000..8886f437fc --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/sqlserver/SQLServerTopTest.java @@ -0,0 +1,26 @@ +package com.alibaba.druid.bvt.sql.sqlserver; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.dialect.sqlserver.parser.SQLServerStatementParser; +import com.alibaba.druid.sql.test.TestUtils; + +public class SQLServerTopTest extends TestCase { + + public void test_isEmpty() throws Exception { + String sql = "SELECT TOP 10 * FROM T"; + + String expect = "SELECT TOP 10 *\nFROM T"; + + SQLServerStatementParser parser = new SQLServerStatementParser(sql); + SQLSelectStatement stmt = (SQLSelectStatement) parser.parseStatementList().get(0); + + String text = TestUtils.outputSqlServer(stmt); + + Assert.assertEquals(expect, text); + + System.out.println(text); + } +} diff --git a/src/test/java/com/alibaba/druid/sql/test/TestUtils.java b/src/test/java/com/alibaba/druid/sql/test/TestUtils.java index 3a1a3ab0da..0a6e6f0d1d 100644 --- a/src/test/java/com/alibaba/druid/sql/test/TestUtils.java +++ b/src/test/java/com/alibaba/druid/sql/test/TestUtils.java @@ -5,6 +5,7 @@ import java.util.List; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import com.alibaba.druid.sql.dialect.sqlserver.visitor.SqlServerOutputVisitor; import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; @@ -20,11 +21,26 @@ public class TestUtils { return out.toString(); } + + public static String outputSqlServer(List stmtList) { + StringBuilder out = new StringBuilder(); + SqlServerOutputVisitor visitor = new SqlServerOutputVisitor(out); + + for (SQLStatement stmt : stmtList) { + stmt.accept(visitor); + } + + return out.toString(); + } public static String outputOracle(SQLStatement... stmtList) { return outputOracle(Arrays.asList(stmtList)); } + public static String outputSqlServer(SQLStatement... stmtList) { + return outputSqlServer(Arrays.asList(stmtList)); + } + public static String output(SQLStatement... stmtList) { return output(Arrays.asList(stmtList)); }