From 9dc9c808f6e8dcc5af60c1f017257f67353d7dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E9=93=81?= Date: Fri, 11 Mar 2016 22:08:13 +0800 Subject: [PATCH] oracle sql parser support keep --- .../com/alibaba/druid/sql/ast/SQLKeep.java | 42 +++++++++++ .../druid/sql/ast/expr/SQLAggregateExpr.java | 16 +++++ .../oracle/parser/OracleExprParser.java | 24 +++++++ .../sql/visitor/SQLASTOutputVisitor.java | 22 +++++- .../druid/sql/visitor/SQLASTVisitor.java | 5 ++ .../sql/visitor/SQLASTVisitorAdapter.java | 10 +++ .../bvt/sql/oracle/OracleSelectTest51.java | 19 +++-- .../bvt/sql/oracle/OracleSelectTest52.java | 19 +++-- .../bvt/sql/oracle/OracleSelectTest53.java | 71 +++++++++++++++++++ 9 files changed, 217 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/alibaba/druid/sql/ast/SQLKeep.java create mode 100644 src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest53.java diff --git a/src/main/java/com/alibaba/druid/sql/ast/SQLKeep.java b/src/main/java/com/alibaba/druid/sql/ast/SQLKeep.java new file mode 100644 index 0000000000..651add56f8 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/ast/SQLKeep.java @@ -0,0 +1,42 @@ +package com.alibaba.druid.sql.ast; + +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +public class SQLKeep extends SQLObjectImpl { + + protected DenseRank denseRank; + + protected SQLOrderBy orderBy; + + @Override + protected void accept0(SQLASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, this.orderBy); + } + visitor.endVisit(this); + } + + public DenseRank getDenseRank() { + return denseRank; + } + + public void setDenseRank(DenseRank denseRank) { + this.denseRank = denseRank; + } + + public SQLOrderBy getOrderBy() { + return orderBy; + } + + public void setOrderBy(SQLOrderBy orderBy) { + if (orderBy != null) { + orderBy.setParent(this); + } + this.orderBy = orderBy; + } + + public static enum DenseRank { + FIRST, // + LAST + } +} diff --git a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLAggregateExpr.java b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLAggregateExpr.java index bc20904ee8..f575429010 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLAggregateExpr.java +++ b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLAggregateExpr.java @@ -21,6 +21,7 @@ import java.util.List; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLExprImpl; +import com.alibaba.druid.sql.ast.SQLKeep; import com.alibaba.druid.sql.ast.SQLOrderBy; import com.alibaba.druid.sql.ast.SQLOver; import com.alibaba.druid.sql.visitor.SQLASTVisitor; @@ -31,6 +32,7 @@ public class SQLAggregateExpr extends SQLExprImpl implements Serializable { protected String methodName; protected SQLAggregateOption option; protected final List arguments = new ArrayList(); + protected SQLKeep keep; protected SQLOver over; protected SQLOrderBy withinGroup; protected boolean ignoreNulls = false; @@ -88,9 +90,23 @@ public class SQLAggregateExpr extends SQLExprImpl implements Serializable { } public void setOver(SQLOver over) { + if (over != null) { + over.setParent(this); + } this.over = over; } + public SQLKeep getKeep() { + return keep; + } + + public void setKeep(SQLKeep keep) { + if (keep != null) { + keep.setParent(this); + } + this.keep = keep; + } + public boolean isIgnoreNulls() { return this.ignoreNulls; } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleExprParser.java b/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleExprParser.java index 7312d85f40..9ad47c2410 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleExprParser.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/oracle/parser/OracleExprParser.java @@ -20,6 +20,8 @@ import java.math.BigInteger; import com.alibaba.druid.sql.ast.SQLDataType; import com.alibaba.druid.sql.ast.SQLDataTypeImpl; import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLKeep; +import com.alibaba.druid.sql.ast.SQLKeep.DenseRank; import com.alibaba.druid.sql.ast.SQLName; import com.alibaba.druid.sql.ast.SQLOrderBy; import com.alibaba.druid.sql.ast.SQLOrderingSpecification; @@ -753,6 +755,28 @@ public class OracleExprParser extends SQLExprParser { aggregateExpr.setWithinGroup(withinGroup); accept(Token.RPAREN); } + + if (lexer.token() == Token.KEEP) { + lexer.nextToken(); + + SQLKeep keep = new SQLKeep(); + accept(Token.LPAREN); + acceptIdentifier("DENSE_RANK"); + if (identifierEquals("FIRST")) { + lexer.nextToken(); + keep.setDenseRank(DenseRank.FIRST); + } else { + acceptIdentifier("LAST"); + keep.setDenseRank(DenseRank.LAST); + } + + SQLOrderBy orderBy = this.parseOrderBy(); + keep.setOrderBy(orderBy); + + aggregateExpr.setKeep(keep); + + accept(Token.RPAREN); + } if (lexer.token() == Token.OVER) { OracleAnalytic over = new OracleAnalytic(); 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 7c1fc91592..853b7c6525 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -30,6 +30,7 @@ import com.alibaba.druid.sql.ast.SQLCommentHint; import com.alibaba.druid.sql.ast.SQLDataType; import com.alibaba.druid.sql.ast.SQLDeclareItem; import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLKeep; import com.alibaba.druid.sql.ast.SQLName; import com.alibaba.druid.sql.ast.SQLObject; import com.alibaba.druid.sql.ast.SQLOrderBy; @@ -566,9 +567,14 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab x.getWithinGroup().accept(this); print(')'); } + + if (x.getKeep() != null) { + print(' '); + x.getKeep().accept(this); + } if (x.getOver() != null) { - print0(" "); + print(' '); x.getOver().accept(this); } return false; @@ -1630,6 +1636,20 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Printab print(')'); return false; } + + @Override + public boolean visit(SQLKeep x) { + if (x.getDenseRank() == SQLKeep.DenseRank.FIRST) { + print0(ucase ? "KEEP (DENSE_RANK FIRST " : "keep (dense_rank first "); + } else { + print0(ucase ? "KEEP (DENSE_RANK LAST " : "keep (dense_rank last "); + } + + x.getOrderBy().accept(this); + print(')'); + + return false; + } @Override public boolean visit(SQLColumnPrimaryKey x) { 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 50ae02c9b6..cf1376459a 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java @@ -18,6 +18,7 @@ package com.alibaba.druid.sql.visitor; import com.alibaba.druid.sql.ast.SQLCommentHint; import com.alibaba.druid.sql.ast.SQLDataType; import com.alibaba.druid.sql.ast.SQLDeclareItem; +import com.alibaba.druid.sql.ast.SQLKeep; import com.alibaba.druid.sql.ast.SQLObject; import com.alibaba.druid.sql.ast.SQLOrderBy; import com.alibaba.druid.sql.ast.SQLOver; @@ -354,6 +355,10 @@ public interface SQLASTVisitor { void endVisit(SQLOver x); boolean visit(SQLOver x); + + void endVisit(SQLKeep x); + + boolean visit(SQLKeep x); void endVisit(SQLColumnPrimaryKey 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 b2f35ce855..4d8d7caca1 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java @@ -19,6 +19,7 @@ import com.alibaba.druid.sql.ast.SQLCommentHint; import com.alibaba.druid.sql.ast.SQLDataType; import com.alibaba.druid.sql.ast.SQLDeclareItem; import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLKeep; import com.alibaba.druid.sql.ast.SQLObject; import com.alibaba.druid.sql.ast.SQLOrderBy; import com.alibaba.druid.sql.ast.SQLOver; @@ -652,6 +653,15 @@ public class SQLASTVisitorAdapter implements SQLASTVisitor { public boolean visit(SQLOver x) { return true; } + + @Override + public void endVisit(SQLKeep x) { + } + + @Override + public boolean visit(SQLKeep x) { + return true; + } @Override public void endVisit(SQLColumnPrimaryKey x) { diff --git a/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest51.java b/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest51.java index e4cf04ec00..cef70d261f 100644 --- a/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest51.java +++ b/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest51.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.Assert; import com.alibaba.druid.sql.OracleTest; +import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.dialect.oracle.parser.OracleStatementParser; import com.alibaba.druid.sql.dialect.oracle.visitor.OracleSchemaStatVisitor; @@ -29,7 +30,7 @@ public class OracleSelectTest51 extends OracleTest { public void test_0() throws Exception { String sql = // - "select TRIM(TRAILING 'M' FROM RTRIM(' testM')) from dual;"; // + "select TRIM(TRAILING 'M' FROM RTRIM(' testM')) from dual;"; // OracleStatementParser parser = new OracleStatementParser(sql); List statementList = parser.parseStatementList(); @@ -51,11 +52,19 @@ public class OracleSelectTest51 extends OracleTest { Assert.assertEquals(0, visitor.getColumns().size()); - String text = TestUtils.outputOracle(stmt); + { + String text = SQLUtils.toOracleString(stmt); - Assert.assertEquals("SELECT TRIM(TRAILING 'M' FROM RTRIM(' testM'))" // - + "\nFROM dual;"// - + "\n", text); + Assert.assertEquals("SELECT TRIM(TRAILING 'M' FROM RTRIM(' testM'))" // + + "\nFROM dual", text); + } + + { + String text = SQLUtils.toOracleString(stmt, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION); + + Assert.assertEquals("select TRIM(TRAILING 'M' from RTRIM(' testM'))" // + + "\nfrom dual", text); + } // Assert.assertTrue(visitor.getColumns().contains(new TableStat.Column("acduser.vw_acd_info", "xzqh"))); diff --git a/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest52.java b/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest52.java index 63821c2a7e..5977f448c3 100644 --- a/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest52.java +++ b/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest52.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.Assert; import com.alibaba.druid.sql.OracleTest; +import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.dialect.oracle.parser.OracleStatementParser; import com.alibaba.druid.sql.dialect.oracle.visitor.OracleSchemaStatVisitor; @@ -29,7 +30,7 @@ public class OracleSelectTest52 extends OracleTest { public void test_0() throws Exception { String sql = // - "select 1 interval from dual"; // + "select 1 interval from dual"; // OracleStatementParser parser = new OracleStatementParser(sql); List statementList = parser.parseStatementList(); @@ -51,11 +52,19 @@ public class OracleSelectTest52 extends OracleTest { Assert.assertEquals(0, visitor.getColumns().size()); - String text = TestUtils.outputOracle(stmt); + { + String text = SQLUtils.toOracleString(stmt); - Assert.assertEquals("SELECT 1 AS INTERVAL" // - + "\nFROM dual;"// - + "\n", text); + Assert.assertEquals("SELECT 1 AS INTERVAL" // + + "\nFROM dual", text); + } + + { + String text = SQLUtils.toOracleString(stmt, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION); + + Assert.assertEquals("select 1 as INTERVAL" // + + "\nfrom dual", text); + } // Assert.assertTrue(visitor.getColumns().contains(new TableStat.Column("acduser.vw_acd_info", "xzqh"))); diff --git a/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest53.java b/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest53.java new file mode 100644 index 0000000000..e814dadd22 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/oracle/OracleSelectTest53.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2101 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; + +import java.util.List; + +import org.junit.Assert; + +import com.alibaba.druid.sql.OracleTest; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.oracle.parser.OracleStatementParser; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleSchemaStatVisitor; + +public class OracleSelectTest53 extends OracleTest { + + public void test_0() throws Exception { + String sql = // + "SELECT MIN(t.deptno) KEEP(DENSE_RANK FIRST ORDER BY t.sal) a FROM emp t"; // + + OracleStatementParser parser = new OracleStatementParser(sql); + List statementList = parser.parseStatementList(); + SQLStatement stmt = statementList.get(0); + print(statementList); + + Assert.assertEquals(1, statementList.size()); + + OracleSchemaStatVisitor visitor = new OracleSchemaStatVisitor(); + stmt.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()); + + Assert.assertEquals(1, visitor.getTables().size()); + + Assert.assertEquals(1, visitor.getColumns().size()); + + { + String text = SQLUtils.toOracleString(stmt); + + Assert.assertEquals("SELECT MIN(t.deptno) KEEP (DENSE_RANK FIRST ORDER BY t.sal) AS a" // + + "\nFROM emp t", text); + } + + { + String text = SQLUtils.toOracleString(stmt, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION); + + Assert.assertEquals("select min(t.deptno) keep (dense_rank first order by t.sal) as a" // + + "\nfrom emp t", text); + } + // Assert.assertTrue(visitor.getColumns().contains(new TableStat.Column("acduser.vw_acd_info", "xzqh"))); + + // Assert.assertTrue(visitor.getOrderByColumns().contains(new TableStat.Column("employees", "last_name"))); + } +}