diff --git a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLListExpr.java b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLListExpr.java new file mode 100644 index 0000000000..41f17e19e4 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLListExpr.java @@ -0,0 +1,27 @@ +package com.alibaba.druid.sql.ast.expr; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLExprImpl; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +public class SQLListExpr extends SQLExprImpl { + + private static final long serialVersionUID = 1L; + + private final List items = new ArrayList(); + + public List getItems() { + return items; + } + + @Override + protected void accept0(SQLASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, items); + } + visitor.endVisit(this); + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/clause/GroupingSetExpr.java b/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/clause/GroupingSetExpr.java new file mode 100644 index 0000000000..e7edc4a1b3 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/clause/GroupingSetExpr.java @@ -0,0 +1,32 @@ +package com.alibaba.druid.sql.dialect.oracle.ast.clause; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLExprImpl; +import com.alibaba.druid.sql.dialect.oracle.ast.visitor.OracleASTVisitor; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +public class GroupingSetExpr extends SQLExprImpl { + + private static final long serialVersionUID = 1L; + private final List parameters = new ArrayList(); + + public List getParameters() { + return parameters; + } + + @Override + protected void accept0(SQLASTVisitor visitor) { + accept0((OracleASTVisitor) visitor); + } + + protected void accept0(OracleASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, parameters); + } + visitor.endVisit(this); + } + +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/visitor/OracleASTVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/visitor/OracleASTVisitor.java index 807d7ac621..f4a31c8260 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/visitor/OracleASTVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/visitor/OracleASTVisitor.java @@ -21,6 +21,7 @@ import com.alibaba.druid.sql.dialect.oracle.ast.OracleHint; import com.alibaba.druid.sql.dialect.oracle.ast.OracleOrderBy; import com.alibaba.druid.sql.dialect.oracle.ast.clause.FlashbackQueryClause.AsOfFlashbackQueryClause; import com.alibaba.druid.sql.dialect.oracle.ast.clause.FlashbackQueryClause.VersionsFlashbackQueryClause; +import com.alibaba.druid.sql.dialect.oracle.ast.clause.GroupingSetExpr; import com.alibaba.druid.sql.dialect.oracle.ast.clause.PartitionExtensionClause; import com.alibaba.druid.sql.dialect.oracle.ast.clause.SampleClause; import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleAggregateExpr; @@ -328,4 +329,8 @@ public interface OracleASTVisitor extends SQLASTVisitor { boolean visit(AsOfFlashbackQueryClause x); void endVisit(AsOfFlashbackQueryClause x); + + boolean visit(GroupingSetExpr x); + + void endVisit(GroupingSetExpr x); } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/visitor/OracleOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/visitor/OracleOutputVisitor.java index 2c8332169d..3640c3b596 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/visitor/OracleOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/oracle/ast/visitor/OracleOutputVisitor.java @@ -33,6 +33,7 @@ import com.alibaba.druid.sql.dialect.oracle.ast.OracleHint; import com.alibaba.druid.sql.dialect.oracle.ast.OracleOrderBy; import com.alibaba.druid.sql.dialect.oracle.ast.clause.FlashbackQueryClause.AsOfFlashbackQueryClause; import com.alibaba.druid.sql.dialect.oracle.ast.clause.FlashbackQueryClause.VersionsFlashbackQueryClause; +import com.alibaba.druid.sql.dialect.oracle.ast.clause.GroupingSetExpr; import com.alibaba.druid.sql.dialect.oracle.ast.clause.PartitionExtensionClause; import com.alibaba.druid.sql.dialect.oracle.ast.clause.SampleClause; import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleAggregateExpr; @@ -1560,5 +1561,19 @@ public class OracleOutputVisitor extends SQLASTOutputVisitor implements OracleAS public void endVisit(AsOfFlashbackQueryClause x) { } + + @Override + public boolean visit(GroupingSetExpr x) { + print("GROUPING SETS"); + print(" ("); + printAndAccept(x.getParameters(), ", "); + print(")"); + return false; + } + + @Override + public void endVisit(GroupingSetExpr x) { + + } } 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 81d158c522..f7dc0c0d61 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 @@ -26,6 +26,7 @@ import com.alibaba.druid.sql.ast.statement.SQLUnionQuery; import com.alibaba.druid.sql.dialect.oracle.ast.OracleHint; import com.alibaba.druid.sql.dialect.oracle.ast.clause.FlashbackQueryClause.AsOfFlashbackQueryClause; import com.alibaba.druid.sql.dialect.oracle.ast.clause.FlashbackQueryClause.VersionsFlashbackQueryClause; +import com.alibaba.druid.sql.dialect.oracle.ast.clause.GroupingSetExpr; import com.alibaba.druid.sql.dialect.oracle.ast.clause.PartitionExtensionClause; import com.alibaba.druid.sql.dialect.oracle.ast.clause.SampleClause; import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleAggregateExpr; @@ -56,7 +57,7 @@ public class OracleSelectParser extends SQLSelectParser { public OracleSelectParser(Lexer lexer){ super(lexer); } - + protected SQLExprParser createExprParser() { return new OracleExprParser(lexer); } @@ -199,9 +200,23 @@ public class OracleSelectParser extends SQLSelectParser { accept(Token.BY); SQLSelectGroupByClause groupBy = new SQLSelectGroupByClause(); - while (true) { - groupBy.getItems().add(this.createExprParser().expr()); - if (!(lexer.token() == (Token.COMMA))) break; + for (;;) { + if (identifierEquals("GROUPING")) { + GroupingSetExpr groupingSet = new GroupingSetExpr(); + lexer.nextToken(); + acceptIdentifier("SETS"); + accept(Token.LPAREN); + createExprParser().exprList(groupingSet.getParameters()); + accept(Token.RPAREN); + groupBy.getItems().add(groupingSet); + } else { + groupBy.getItems().add(this.createExprParser().expr()); + } + + if (!(lexer.token() == (Token.COMMA))) { + break; + } + lexer.nextToken(); } @@ -246,8 +261,7 @@ public class OracleSelectParser extends SQLSelectParser { lexer.nextToken(); accept(Token.BY); - - + if (lexer.token() == Token.PRIOR) { lexer.nextToken(); hierachical.setPrior(true); @@ -301,40 +315,40 @@ public class OracleSelectParser extends SQLSelectParser { private void parseTableSourceQueryTableExpr(OracleSelectTableReference tableReference) { tableReference.setExpr(this.createExprParser().expr()); - + if (identifierEquals("SAMPLE")) { lexer.nextToken(); - + SampleClause sample = new SampleClause(); - + if (identifierEquals("BLOCK")) { sample.setBlock(true); } - + accept(Token.LPAREN); - + if (lexer.token() != Token.LITERAL_INT) { throw new ParserException("syntax error : " + lexer.token()); } sample.setPercent(lexer.integerValue().intValue()); lexer.nextToken(); - + accept(Token.RPAREN); - + if (identifierEquals("SEED")) { lexer.nextToken(); accept(Token.LPAREN); sample.setSeedValue(expr()); accept(Token.RPAREN); } - + tableReference.setSampleClause(sample); } if (identifierEquals("PARTITION")) { lexer.nextToken(); PartitionExtensionClause partition = new PartitionExtensionClause(); - + if (lexer.token() == Token.LPAREN) { lexer.nextToken(); partition.setPartition(createExprParser().name()); @@ -345,15 +359,15 @@ public class OracleSelectParser extends SQLSelectParser { createExprParser().names(partition.getFor()); accept(Token.RPAREN); } - + tableReference.setPartition(partition); } - + if (identifierEquals("SUBPARTITION")) { lexer.nextToken(); PartitionExtensionClause partition = new PartitionExtensionClause(); partition.setSubPartition(true); - + if (lexer.token() == Token.LPAREN) { lexer.nextToken(); partition.setPartition(createExprParser().name()); @@ -364,16 +378,16 @@ public class OracleSelectParser extends SQLSelectParser { createExprParser().names(partition.getFor()); accept(Token.RPAREN); } - + tableReference.setPartition(partition); } - + if (lexer.token() == Token.AS) { lexer.nextToken(); - + if (identifierEquals("OF")) { lexer.nextToken(); - + AsOfFlashbackQueryClause clause = new AsOfFlashbackQueryClause(); if (identifierEquals("SCN")) { clause.setType(AsOfFlashbackQueryClause.Type.SCN); @@ -383,17 +397,17 @@ public class OracleSelectParser extends SQLSelectParser { clause.setType(AsOfFlashbackQueryClause.Type.TIMESTAMP); } clause.setExpr(createExprParser().primary()); - + tableReference.setFlashback(clause); } else { throw new SQLParseException("TODO"); } } else if (identifierEquals("VERSIONS")) { lexer.nextToken(); - + if (lexer.token() == Token.BETWEEN) { lexer.nextToken(); - + VersionsFlashbackQueryClause clause = new VersionsFlashbackQueryClause(); if (identifierEquals("SCN")) { clause.setType(AsOfFlashbackQueryClause.Type.SCN); @@ -402,21 +416,21 @@ public class OracleSelectParser extends SQLSelectParser { acceptIdentifier("TIMESTAMP"); clause.setType(AsOfFlashbackQueryClause.Type.TIMESTAMP); } - + SQLBinaryOpExpr binaryExpr = (SQLBinaryOpExpr) createExprParser().expr(); if (binaryExpr.getOperator() != SQLBinaryOperator.BooleanAnd) { throw new SQLParseException("syntax error : " + binaryExpr.getOperator()); } - + clause.setBegin(binaryExpr.getLeft()); clause.setEnd(binaryExpr.getRight()); - + tableReference.setFlashback(clause); } else { throw new SQLParseException("TODO"); } } - + } protected SQLTableSource parseTableSourceRest(SQLTableSource tableSource) { @@ -465,7 +479,7 @@ public class OracleSelectParser extends SQLSelectParser { lexer.nextToken(); joinType = OracleSelectJoin.JoinType.JOIN; } - + if (lexer.token() == (Token.COMMA)) { lexer.nextToken(); joinType = OracleSelectJoin.JoinType.COMMA; @@ -489,7 +503,7 @@ public class OracleSelectParser extends SQLSelectParser { return join; } - + return tableSource; } 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 0e09aec11e..8209c884f9 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java @@ -40,6 +40,7 @@ import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.druid.sql.ast.expr.SQLInListExpr; import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr; import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; +import com.alibaba.druid.sql.ast.expr.SQLListExpr; import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; import com.alibaba.druid.sql.ast.expr.SQLNCharExpr; import com.alibaba.druid.sql.ast.expr.SQLNotExpr; @@ -148,6 +149,16 @@ public class SQLExprParser extends SQLParser { case LPAREN: lexer.nextToken(); sqlExpr = expr(); + if (lexer.token() == Token.COMMA) { + SQLListExpr listExpr = new SQLListExpr(); + listExpr.getItems().add(sqlExpr); + do { + lexer.nextToken(); + listExpr.getItems().add(expr()); + } while (lexer.token() == Token.COMMA); + + sqlExpr = listExpr; + } accept(Token.RPAREN); break; case INSERT: 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 9e2937242f..4c2cd5f3f0 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -40,6 +40,7 @@ import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.druid.sql.ast.expr.SQLInListExpr; import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr; import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; +import com.alibaba.druid.sql.ast.expr.SQLListExpr; import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; import com.alibaba.druid.sql.ast.expr.SQLNCharExpr; import com.alibaba.druid.sql.ast.expr.SQLNotExpr; @@ -808,4 +809,13 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter { return false; } + + @Override + public boolean visit(SQLListExpr x) { + print("("); + printAndAccept(x.getItems(), ", "); + print(")"); + + 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 d996c758aa..7986c698c1 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java @@ -38,6 +38,7 @@ import com.alibaba.druid.sql.ast.expr.SQLInListExpr; import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr; import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; import com.alibaba.druid.sql.ast.expr.SQLIntervalLiteralExpr; +import com.alibaba.druid.sql.ast.expr.SQLListExpr; import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; import com.alibaba.druid.sql.ast.expr.SQLNCharExpr; import com.alibaba.druid.sql.ast.expr.SQLNotExpr; @@ -319,4 +320,8 @@ public interface SQLASTVisitor { void endVisit(SQLInSubQueryExpr x); boolean visit(SQLInSubQueryExpr x); + + void endVisit(SQLListExpr x); + + boolean visit(SQLListExpr 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 d3c2a75c7f..59a7d29f73 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java @@ -38,6 +38,7 @@ import com.alibaba.druid.sql.ast.expr.SQLInListExpr; import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr; import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; import com.alibaba.druid.sql.ast.expr.SQLIntervalLiteralExpr; +import com.alibaba.druid.sql.ast.expr.SQLListExpr; import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; import com.alibaba.druid.sql.ast.expr.SQLNCharExpr; import com.alibaba.druid.sql.ast.expr.SQLNotExpr; @@ -541,4 +542,14 @@ public class SQLASTVisitorAdapter implements SQLASTVisitor { public boolean visit(SQLInSubQueryExpr x) { return true; } + + @Override + public void endVisit(SQLListExpr x) { + + } + + @Override + public boolean visit(SQLListExpr x) { + return false; + } } diff --git a/src/test/java/com/alibaba/druid/sql/oracle/bvt/OracleSQLParserResourceTest.java b/src/test/java/com/alibaba/druid/sql/oracle/bvt/OracleSQLParserResourceTest.java index ca258dd6fd..ab92de70bf 100644 --- a/src/test/java/com/alibaba/druid/sql/oracle/bvt/OracleSQLParserResourceTest.java +++ b/src/test/java/com/alibaba/druid/sql/oracle/bvt/OracleSQLParserResourceTest.java @@ -27,7 +27,10 @@ public class OracleSQLParserResourceTest extends TestCase { // exec_test("bvt/parser/oracle-8.txt"); // exec_test("bvt/parser/oracle-9.txt"); // exec_test("bvt/parser/oracle-10.txt"); - exec_test("bvt/parser/oracle-11.txt"); +// exec_test("bvt/parser/oracle-11.txt"); +// exec_test("bvt/parser/oracle-12.txt"); +// exec_test("bvt/parser/oracle-13.txt"); + exec_test("bvt/parser/oracle-14.txt"); } diff --git a/src/test/resources/bvt/parser/oracle-12.txt b/src/test/resources/bvt/parser/oracle-12.txt new file mode 100644 index 0000000000..2d4c93aea2 --- /dev/null +++ b/src/test/resources/bvt/parser/oracle-12.txt @@ -0,0 +1,9 @@ +SELECT department_id, MIN(salary), MAX (salary) + FROM employees + GROUP BY department_id + ORDER BY department_id; +--------------------------- +SELECT department_id, MIN(salary), MAX(salary) +FROM employees +GROUP BY department_id +ORDER BY department_id; \ No newline at end of file diff --git a/src/test/resources/bvt/parser/oracle-13.txt b/src/test/resources/bvt/parser/oracle-13.txt new file mode 100644 index 0000000000..a121104253 --- /dev/null +++ b/src/test/resources/bvt/parser/oracle-13.txt @@ -0,0 +1,15 @@ +SELECT DECODE(GROUPING(department_name), 1, 'All Departments', + department_name) AS department_name, + DECODE(GROUPING(job_id), 1, 'All Jobs', job_id) AS job_id, + COUNT(*) "Total Empl", AVG(salary) * 12 "Average Sal" + FROM employees e, departments d + WHERE d.department_id = e.department_id + GROUP BY CUBE (department_name, job_id) + ORDER BY department_name, job_id; +--------------------------- +SELECT DECODE(GROUPING(department_name), 1, 'All Departments', department_name) AS department_name, DECODE(GROUPING(job_id), 1, 'All Jobs', job_id) AS job_id, COUNT(*) AS COUNT, AVG(salary) * 12 AS salary +FROM employees e , departments d +WHERE d.department_id = e.department_id +GROUP BY CUBE(department_name, job_id) +ORDER BY department_name, job_id; + diff --git a/src/test/resources/bvt/parser/oracle-14.txt b/src/test/resources/bvt/parser/oracle-14.txt new file mode 100644 index 0000000000..d0055a0b6a --- /dev/null +++ b/src/test/resources/bvt/parser/oracle-14.txt @@ -0,0 +1,21 @@ +SELECT channel_desc, calendar_month_desc, co.country_id, + TO_CHAR(sum(amount_sold) , '9,999,999,999') SALES$ + FROM sales, customers, times, channels, countries co + WHERE sales.time_id=times.time_id + AND sales.cust_id=customers.cust_id + AND sales.channel_id= channels.channel_id + AND customers.country_id = co.country_id + AND channels.channel_desc IN ('Direct Sales', 'Internet') + AND times.calendar_month_desc IN ('2000-09', '2000-10') + AND co.country_iso_code IN ('UK', 'US') + GROUP BY GROUPING SETS( + (channel_desc, calendar_month_desc, co.country_id), + (channel_desc, co.country_id), + (calendar_month_desc, co.country_id) ); +--------------------------- +SELECT channel_desc, calendar_month_desc, co.country_id, TO_CHAR(sum(amount_sold), '9,999,999,999') AS SALES$ +FROM sales , customers , times , channels , countries co +WHERE sales.time_id = times.time_id AND (sales.cust_id = customers.cust_id AND (sales.channel_id = channels.channel_id AND (customers.country_id = co.country_id AND (channels.channel_desc IN ('Direct Sales', 'Internet') AND (times.calendar_month_desc IN ('2000-09', '2000-10') AND co.country_iso_code IN ('UK', 'US')))))) +GROUP BY GROUPING SETS ((channel_desc, calendar_month_desc, co.country_id), (channel_desc, co.country_id), (calendar_month_desc, co.country_id)); + +