diff --git a/src/main/java/com/alibaba/druid/sql/ast/SQLOver.java b/src/main/java/com/alibaba/druid/sql/ast/SQLOver.java index 8aa9ef0e77..d83e41734b 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/SQLOver.java +++ b/src/main/java/com/alibaba/druid/sql/ast/SQLOver.java @@ -28,6 +28,20 @@ public class SQLOver extends SQLObjectImpl { // for db2 protected SQLExpr of; + protected SQLExpr windowing; + protected WindowingType windowingType = WindowingType.ROWS; + + protected boolean windowingPreceding; + protected boolean windowingFollowing; + + protected SQLExpr windowingBetweenBegin; + protected boolean windowingBetweenBeginPreceding; + protected boolean windowingBetweenBeginFollowing; + + protected SQLExpr windowingBetweenEnd; + protected boolean windowingBetweenEndPreceding; + protected boolean windowingBetweenEndFollowing; + public SQLOver(){ } @@ -72,35 +86,129 @@ public class SQLOver extends SQLObjectImpl { return partitionBy; } + public SQLExpr getWindowing() { + return windowing; + } + + public void setWindowing(SQLExpr windowing) { + this.windowing = windowing; + } + + public WindowingType getWindowingType() { + return windowingType; + } + + public void setWindowingType(WindowingType windowingType) { + this.windowingType = windowingType; + } + + public boolean isWindowingPreceding() { + return windowingPreceding; + } + + public void setWindowingPreceding(boolean windowingPreceding) { + this.windowingPreceding = windowingPreceding; + } + + public boolean isWindowingFollowing() { + return windowingFollowing; + } + + public void setWindowingFollowing(boolean windowingFollowing) { + this.windowingFollowing = windowingFollowing; + } + + public SQLExpr getWindowingBetweenBegin() { + return windowingBetweenBegin; + } + + public void setWindowingBetweenBegin(SQLExpr windowingBetweenBegin) { + this.windowingBetweenBegin = windowingBetweenBegin; + } + + public boolean isWindowingBetweenBeginPreceding() { + return windowingBetweenBeginPreceding; + } + + public void setWindowingBetweenBeginPreceding(boolean windowingBetweenBeginPreceding) { + this.windowingBetweenBeginPreceding = windowingBetweenBeginPreceding; + } + + public boolean isWindowingBetweenBeginFollowing() { + return windowingBetweenBeginFollowing; + } + + public void setWindowingBetweenBeginFollowing(boolean windowingBetweenBeginFollowing) { + this.windowingBetweenBeginFollowing = windowingBetweenBeginFollowing; + } + + public SQLExpr getWindowingBetweenEnd() { + return windowingBetweenEnd; + } + + public void setWindowingBetweenEnd(SQLExpr windowingBetweenEnd) { + this.windowingBetweenEnd = windowingBetweenEnd; + } + + public boolean isWindowingBetweenEndPreceding() { + return windowingBetweenEndPreceding; + } + + public void setWindowingBetweenEndPreceding(boolean windowingBetweenEndPreceding) { + this.windowingBetweenEndPreceding = windowingBetweenEndPreceding; + } + + public boolean isWindowingBetweenEndFollowing() { + return windowingBetweenEndFollowing; + } + + public void setWindowingBetweenEndFollowing(boolean windowingBetweenEndFollowing) { + this.windowingBetweenEndFollowing = windowingBetweenEndFollowing; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SQLOver sqlOver = (SQLOver) o; + + if (windowingPreceding != sqlOver.windowingPreceding) return false; + if (windowingFollowing != sqlOver.windowingFollowing) return false; + if (windowingBetweenBeginPreceding != sqlOver.windowingBetweenBeginPreceding) return false; + if (windowingBetweenBeginFollowing != sqlOver.windowingBetweenBeginFollowing) return false; + if (windowingBetweenEndPreceding != sqlOver.windowingBetweenEndPreceding) return false; + if (windowingBetweenEndFollowing != sqlOver.windowingBetweenEndFollowing) return false; + if (partitionBy != null ? !partitionBy.equals(sqlOver.partitionBy) : sqlOver.partitionBy != null) return false; + if (orderBy != null ? !orderBy.equals(sqlOver.orderBy) : sqlOver.orderBy != null) return false; + if (of != null ? !of.equals(sqlOver.of) : sqlOver.of != null) return false; + if (windowing != null ? !windowing.equals(sqlOver.windowing) : sqlOver.windowing != null) return false; + if (windowingType != sqlOver.windowingType) return false; + if (windowingBetweenBegin != null ? !windowingBetweenBegin.equals(sqlOver.windowingBetweenBegin) : sqlOver.windowingBetweenBegin != null) + return false; + return windowingBetweenEnd != null ? windowingBetweenEnd.equals(sqlOver.windowingBetweenEnd) : sqlOver.windowingBetweenEnd == null; + + } + @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((orderBy == null) ? 0 : orderBy.hashCode()); - result = prime * result + partitionBy.hashCode(); + int result = partitionBy != null ? partitionBy.hashCode() : 0; + result = 31 * result + (orderBy != null ? orderBy.hashCode() : 0); + result = 31 * result + (of != null ? of.hashCode() : 0); + result = 31 * result + (windowing != null ? windowing.hashCode() : 0); + result = 31 * result + (windowingType != null ? windowingType.hashCode() : 0); + result = 31 * result + (windowingPreceding ? 1 : 0); + result = 31 * result + (windowingFollowing ? 1 : 0); + result = 31 * result + (windowingBetweenBegin != null ? windowingBetweenBegin.hashCode() : 0); + result = 31 * result + (windowingBetweenBeginPreceding ? 1 : 0); + result = 31 * result + (windowingBetweenBeginFollowing ? 1 : 0); + result = 31 * result + (windowingBetweenEnd != null ? windowingBetweenEnd.hashCode() : 0); + result = 31 * result + (windowingBetweenEndPreceding ? 1 : 0); + result = 31 * result + (windowingBetweenEndFollowing ? 1 : 0); return result; } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - SQLOver other = (SQLOver) obj; - if (orderBy == null) { - if (other.orderBy != null) { - return false; - } - } else if (!orderBy.equals(other.orderBy)) { - return false; - } - return partitionBy.equals(other.partitionBy); + public static enum WindowingType { + ROWS, RANGE } - } 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 0272747295..92c8b5c6cf 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java @@ -1078,6 +1078,57 @@ public class SQLExprParser extends SQLParser { over.setOf(of); } + SQLOver.WindowingType windowingType = null; + if (identifierEquals("ROWS")) { + windowingType = SQLOver.WindowingType.ROWS; + } else if (identifierEquals("RANGE")) { + windowingType = SQLOver.WindowingType.RANGE; + } + + if (windowingType != null) { + over.setWindowingType(windowingType); + lexer.nextToken(); + + if (lexer.token == Token.BETWEEN) { + lexer.nextToken(); + SQLIntegerExpr rowsBegin = (SQLIntegerExpr) this.primary(); + over.setWindowingBetweenBegin(rowsBegin); + + if (identifierEquals("PRECEDING")) { + over.setWindowingBetweenBeginPreceding(true); + lexer.nextToken(); + } else if (identifierEquals("FOLLOWING")) { + over.setWindowingBetweenBeginFollowing(true); + lexer.nextToken(); + } + + accept(Token.AND); + + SQLIntegerExpr betweenEnd = (SQLIntegerExpr) this.primary(); + over.setWindowingBetweenEnd(betweenEnd); + + if (identifierEquals("PRECEDING")) { + over.setWindowingBetweenEndPreceding(true); + lexer.nextToken(); + } else if (identifierEquals("FOLLOWING")) { + over.setWindowingBetweenEndFollowing(true); + lexer.nextToken(); + } + + } else { + SQLIntegerExpr rowsExpr = (SQLIntegerExpr) this.primary(); + over.setWindowing(rowsExpr); + + if (identifierEquals("PRECEDING")) { + over.setWindowingPreceding(true); + lexer.nextToken(); + } else if (identifierEquals("FOLLOWING")) { + over.setWindowingFollowing(true); + lexer.nextToken(); + } + } + } + accept(Token.RPAREN); aggregateExpr.setOver(over); } 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 05325e0896..41984b4520 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -1752,6 +1752,47 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Paramet print0(ucase ? " OF " : " of "); x.getOf().accept(this); } + + if (x.getWindowing() != null) { + if (SQLOver.WindowingType.ROWS.equals(x.getWindowingType())) { + print0(ucase ? " ROWS " : " rows "); + } else if (SQLOver.WindowingType.RANGE.equals(x.getWindowingType())) { + print0(ucase ? " RANGE " : " range "); + } + + x.getWindowing().accept(this); + if (x.isWindowingPreceding()) { + print0(ucase ? " PRECEDING" : " preceding"); + } else if (x.isWindowingFollowing()) { + print0(ucase ? " FOLLOWING" : " following"); + } + } + + if (x.getWindowingBetweenBegin() != null) { + if (SQLOver.WindowingType.ROWS.equals(x.getWindowingType())) { + print0(ucase ? " ROWS BETWEEN " : " rows between "); + } else if (SQLOver.WindowingType.RANGE.equals(x.getWindowingType())) { + print0(ucase ? " RANGE BETWEEN " : " range between "); + } + + x.getWindowingBetweenBegin().accept(this); + + if (x.isWindowingBetweenBeginPreceding()) { + print0(ucase ? " PRECEDING" : " preceding"); + } else if (x.isWindowingBetweenBeginFollowing()) { + print0(ucase ? " FOLLOWING" : " following"); + } + + print0(ucase ? " AND " : " and "); + + x.getWindowingBetweenEnd().accept(this); + + if (x.isWindowingBetweenEndPreceding()) { + print0(ucase ? " PRECEDING" : " preceding"); + } else if (x.isWindowingBetweenEndFollowing()) { + print0(ucase ? " FOLLOWING" : " following"); + } + } print(')'); return false; diff --git a/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsSelectTest_over_rows.java b/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsSelectTest_over_rows.java new file mode 100644 index 0000000000..364f015f7c --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsSelectTest_over_rows.java @@ -0,0 +1,58 @@ +/* + * 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.odps; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.visitor.SchemaStatVisitor; +import com.alibaba.druid.util.JdbcConstants; +import junit.framework.TestCase; +import org.junit.Assert; + +import java.util.List; + +public class OdpsSelectTest_over_rows extends TestCase { + + public void test_select() throws Exception { + String sql = "SELECT sum(c) over(order by ds rows 9999 preceding) s, ds FROM t1";// + Assert.assertEquals("SELECT SUM(c) OVER (ORDER BY ds ROWS 9999 PRECEDING) AS s\n" + + "\t, ds\n" + + "FROM t1", SQLUtils.formatOdps(sql)); + Assert.assertEquals("select sum(c) over (order by ds rows 9999 preceding) as s\n" + + "\t, ds\n" + + "from t1", SQLUtils.formatOdps(sql, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION)); + + List statementList = SQLUtils.parseStatements(sql, JdbcConstants.ODPS); + SQLStatement stmt = statementList.get(0); + + Assert.assertEquals(1, statementList.size()); + + SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(JdbcConstants.ODPS); + 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(0, visitor.getConditions().size()); + +// Assert.assertTrue(visitor.getColumns().contains(new Column("abc", "name"))); + } + +} diff --git a/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsSelectTest_over_rows_1.java b/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsSelectTest_over_rows_1.java new file mode 100644 index 0000000000..359d67e2f9 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsSelectTest_over_rows_1.java @@ -0,0 +1,75 @@ +/* + * 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.odps; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.visitor.SchemaStatVisitor; +import com.alibaba.druid.util.JdbcConstants; +import junit.framework.TestCase; +import org.junit.Assert; + +import java.util.List; + +public class OdpsSelectTest_over_rows_1 extends TestCase { + + public void test_select() throws Exception { + String sql = "select last_name, first_name, department_id, hire_date, salary,\n" + + " SUM (salary)\n" + + " OVER (PARTITION BY department_id ORDER BY hire_date\n" + + " RANGE BETWEEN 365 PRECEDING AND 365 FOLLOWING) department_total\n" + + " from employee\n" + + " order by department_id, hire_date;";// + Assert.assertEquals("SELECT last_name\n" + + "\t, first_name\n" + + "\t, department_id\n" + + "\t, hire_date\n" + + "\t, salary\n" + + "\t, SUM(salary) OVER (PARTITION BY department_id ORDER BY hire_date RANGE BETWEEN 365 PRECEDING AND 365 FOLLOWING) AS department_total\n" + + "FROM employee\n" + + "ORDER BY department_id, \n" + + "\thire_date;", SQLUtils.formatOdps(sql)); + Assert.assertEquals("select last_name\n" + + "\t, first_name\n" + + "\t, department_id\n" + + "\t, hire_date\n" + + "\t, salary\n" + + "\t, sum(salary) over (partition by department_id order by hire_date range between 365 preceding and 365 following) as department_total\n" + + "from employee\n" + + "order by department_id, \n" + + "\thire_date;", SQLUtils.formatOdps(sql, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION)); + + List statementList = SQLUtils.parseStatements(sql, JdbcConstants.ODPS); + SQLStatement stmt = statementList.get(0); + + Assert.assertEquals(1, statementList.size()); + + SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(JdbcConstants.ODPS); + 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(5, visitor.getColumns().size()); + Assert.assertEquals(0, visitor.getConditions().size()); + +// Assert.assertTrue(visitor.getColumns().contains(new Column("abc", "name"))); + } + +} diff --git a/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsSelectTest_over_rows_2.java b/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsSelectTest_over_rows_2.java new file mode 100644 index 0000000000..d4a6e7a5e6 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsSelectTest_over_rows_2.java @@ -0,0 +1,78 @@ +/* + * 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.odps; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.visitor.SchemaStatVisitor; +import com.alibaba.druid.util.JdbcConstants; +import junit.framework.TestCase; +import org.junit.Assert; + +import java.util.List; + +public class OdpsSelectTest_over_rows_2 extends TestCase { + + public void test_select() throws Exception { + String sql = "select last_name, first_name, department_id, hire_date, salary,\n" + + " SUM (salary)\n" + + " OVER (PARTITION BY department_id ORDER BY last_name, first_name\n" + + " RANGE 90 PRECEDING) department_total\n" + + " from employee\n" + + " order by department_id, hire_date;\n" + + "";// + Assert.assertEquals("SELECT last_name\n" + + "\t, first_name\n" + + "\t, department_id\n" + + "\t, hire_date\n" + + "\t, salary\n" + + "\t, SUM(salary) OVER (PARTITION BY department_id ORDER BY last_name, \n" + + "\t\tfirst_name RANGE 90 PRECEDING) AS department_total\n" + + "FROM employee\n" + + "ORDER BY department_id, \n" + + "\thire_date;", SQLUtils.formatOdps(sql)); + Assert.assertEquals("select last_name\n" + + "\t, first_name\n" + + "\t, department_id\n" + + "\t, hire_date\n" + + "\t, salary\n" + + "\t, sum(salary) over (partition by department_id order by last_name, \n" + + "\t\tfirst_name range 90 preceding) as department_total\n" + + "from employee\n" + + "order by department_id, \n" + + "\thire_date;", SQLUtils.formatOdps(sql, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION)); + + List statementList = SQLUtils.parseStatements(sql, JdbcConstants.ODPS); + SQLStatement stmt = statementList.get(0); + + Assert.assertEquals(1, statementList.size()); + + SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(JdbcConstants.ODPS); + 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(5, visitor.getColumns().size()); + Assert.assertEquals(0, visitor.getConditions().size()); + +// Assert.assertTrue(visitor.getColumns().contains(new Column("abc", "name"))); + } + +}