diff --git a/core/src/main/java/com/alibaba/druid/sql/ast/statement/SQLSelectQueryBlock.java b/core/src/main/java/com/alibaba/druid/sql/ast/statement/SQLSelectQueryBlock.java index 48da09a779..a6c07941d6 100644 --- a/core/src/main/java/com/alibaba/druid/sql/ast/statement/SQLSelectQueryBlock.java +++ b/core/src/main/java/com/alibaba/druid/sql/ast/statement/SQLSelectQueryBlock.java @@ -46,6 +46,7 @@ public class SQLSelectQueryBlock extends SQLSelectQueryBase implements SQLReplac protected SQLSelectGroupByClause groupBy; protected List windows; + protected SQLExpr qualify; protected SQLOrderBy orderBy; protected boolean forUpdate; protected boolean noWait; @@ -113,6 +114,17 @@ public class SQLSelectQueryBlock extends SQLSelectQueryBase implements SQLReplac this.windows.add(x); } + public SQLExpr getQualify() { + return qualify; + } + + public void setQualify(SQLExpr x) { + if (x != null) { + x.setParent(this); + } + this.qualify = x; + } + public SQLExpr getWhere() { return this.where; } diff --git a/core/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsLexer.java b/core/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsLexer.java index ea984f999d..1aa44ef31b 100644 --- a/core/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsLexer.java +++ b/core/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsLexer.java @@ -45,6 +45,7 @@ public class OdpsLexer extends Lexer { map.put("RLIKE", Token.RLIKE); map.put("DIV", Token.DIV); map.put("LATERAL", Token.LATERAL); + map.put("QUALIFY", Token.QUALIFY); map.put(";", Token.SEMI); DEFAULT_ODPS_KEYWORDS = new Keywords(map); diff --git a/core/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsSelectParser.java b/core/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsSelectParser.java index 78e5923797..59478c5515 100644 --- a/core/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsSelectParser.java +++ b/core/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsSelectParser.java @@ -15,6 +15,7 @@ */ package com.alibaba.druid.sql.dialect.odps.parser; +import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLLimit; import com.alibaba.druid.sql.ast.SQLObject; import com.alibaba.druid.sql.ast.SQLSetQuantifier; @@ -161,11 +162,17 @@ public class OdpsSelectParser extends SQLSelectParser { parseGroupBy(queryBlock); } + parseGroupBy(queryBlock); + if (lexer.identifierEquals(FnvHash.Constants.WINDOW)) { parseWindow(queryBlock); } - parseGroupBy(queryBlock); + if (lexer.token() == Token.QUALIFY) { + lexer.nextToken(); + SQLExpr qualify = this.exprParser.expr(); + queryBlock.setQualify(qualify); + } queryBlock.setOrderBy(this.exprParser.parseOrderBy()); queryBlock.setZOrderBy(this.exprParser.parseZOrderBy()); diff --git a/core/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsOutputVisitor.java b/core/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsOutputVisitor.java index 2d0703e289..21827b821a 100644 --- a/core/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsOutputVisitor.java +++ b/core/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsOutputVisitor.java @@ -473,6 +473,13 @@ public class OdpsOutputVisitor extends HiveOutputVisitor implements OdpsASTVisit printAndAccept(windows, ", "); } + SQLExpr qualify = x.getQualify(); + if (qualify != null) { + println(); + print0(ucase ? "QUALIFY " : "qualify "); + qualify.accept(this); + } + if (x.getOrderBy() != null) { println(); x.getOrderBy().accept(this); diff --git a/core/src/main/java/com/alibaba/druid/util/FnvHash.java b/core/src/main/java/com/alibaba/druid/util/FnvHash.java index 92ded03e71..419f1648dc 100644 --- a/core/src/main/java/com/alibaba/druid/util/FnvHash.java +++ b/core/src/main/java/com/alibaba/druid/util/FnvHash.java @@ -974,6 +974,7 @@ public final class FnvHash { long RETURNS = fnv1a_64_lower("RETURNS"); long ROWTYPE = fnv1a_64_lower("ROWTYPE"); long WINDOW = fnv1a_64_lower("WINDOW"); + long QUALIFY = fnv1a_64_lower("QUALIFY"); long MULTIVALUE = fnv1a_64_lower("MULTIVALUE"); long OPTIONALLY = fnv1a_64_lower("OPTIONALLY"); long ENCLOSED = fnv1a_64_lower("ENCLOSED"); diff --git a/core/src/test/java/com/alibaba/druid/TestUtil.java b/core/src/test/java/com/alibaba/druid/TestUtil.java index dfcc4e9d8f..072161ed13 100644 --- a/core/src/test/java/com/alibaba/druid/TestUtil.java +++ b/core/src/test/java/com/alibaba/druid/TestUtil.java @@ -70,7 +70,7 @@ public class TestUtil { try (Reader reader = new InputStreamReader( contextClassLoader .getResourceAsStream(path), "UTF-8") - ){ + ) { return Utils.read(reader); } catch (IOException ignored) { return null; diff --git a/core/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsResourceTest.java b/core/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsResourceTest.java index a1489d8f36..63e78de4d1 100644 --- a/core/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsResourceTest.java +++ b/core/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsResourceTest.java @@ -15,37 +15,29 @@ */ package com.alibaba.druid.bvt.sql.odps; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; import java.util.List; -import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; +import com.alibaba.druid.TestUtil; import junit.framework.TestCase; import org.junit.Assert; import com.alibaba.druid.sql.ast.SQLStatement; -import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; import com.alibaba.druid.sql.dialect.odps.parser.OdpsStatementParser; import com.alibaba.druid.sql.dialect.odps.visitor.OdpsSchemaStatVisitor; import com.alibaba.druid.sql.visitor.SchemaStatVisitor; -import com.alibaba.druid.util.JdbcUtils; -import com.alibaba.druid.util.Utils; public class OdpsResourceTest extends TestCase { public void test_0() throws Exception { - exec_test("bvt/parser/odps-0.sql"); + exec_test("bvt/parser/odps-0.txt"); + } + + public void test_9() throws Exception { + exec_test("bvt/parser/odps-9.txt"); } public void exec_test(String resource) throws Exception { -// System.out.println(resource); - InputStream is = null; - - is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource); - Reader reader = new InputStreamReader(is, "UTF-8"); - String input = Utils.read(reader); - JdbcUtils.close(reader); + String input = TestUtil.getResource(resource); String[] items = input.split("---------------------------"); String sql = items[0].trim(); String expect = null; @@ -63,29 +55,9 @@ public class OdpsResourceTest extends TestCase { SchemaStatVisitor visitor = new OdpsSchemaStatVisitor(); stmt.accept(visitor); -// System.out.println(sql); -// System.out.println("Tables : " + visitor.getTables()); -// System.out.println("fields : " + visitor.getColumns()); -// -// System.out.println(); -// System.out.println("---------------------------"); -// System.out.println(SQLUtils.toOdpsString(stmt)); + if (expect != null) { + String result = stmt.toString(); + assertEquals(expect, result); + } } - - void mergValidate(String sql, String expect) { - MySqlStatementParser parser = new MySqlStatementParser(sql); - List statementList = parser.parseStatementList(); - SQLStatement statemen = statementList.get(0); - - Assert.assertEquals(1, statementList.size()); - - StringBuilder out = new StringBuilder(); - MySqlOutputVisitor visitor = new MySqlOutputVisitor(out, true); - statemen.accept(visitor); - - System.out.println(out.toString()); - - Assert.assertEquals(expect, out.toString()); - } - } diff --git a/core/src/test/java/com/alibaba/druid/sql/odps/OdpsFileTest.java b/core/src/test/java/com/alibaba/druid/sql/odps/OdpsFileTest.java index 5dd1d67308..38b6d0ac17 100644 --- a/core/src/test/java/com/alibaba/druid/sql/odps/OdpsFileTest.java +++ b/core/src/test/java/com/alibaba/druid/sql/odps/OdpsFileTest.java @@ -35,7 +35,7 @@ import com.alibaba.druid.util.Utils; public class OdpsFileTest extends TestCase { public void test_0() throws Exception { - exec_test("bvt/parser/odps-0.sql"); + exec_test("bvt/parser/odps-0.txt"); } public void exec_test(String resource) throws Exception { diff --git a/core/src/test/resources/bvt/parser/odps-0.sql b/core/src/test/resources/bvt/parser/odps-0.txt similarity index 51% rename from core/src/test/resources/bvt/parser/odps-0.sql rename to core/src/test/resources/bvt/parser/odps-0.txt index 93c6b429de..1b9bc9d21f 100644 --- a/core/src/test/resources/bvt/parser/odps-0.sql +++ b/core/src/test/resources/bvt/parser/odps-0.txt @@ -10,15 +10,17 @@ create table tt_xx ) comment 'xx comment xxx' partitioned by (pt string comment '按天分区字段', seg string comment '时间跨度') ; --------------------------- -create table tt_xx -( - f1 string, - f2 string comment 'xxx', - f3 string comment 'xxx', - gmt_first datetime comment '首次时间', - gmt_last datetime comment '末次时间', - pv_cnt bigint comment 'PV数量', - pv_day_cnt bigint comment 'PV天数' -) comment 'xx comment xxx' -partitioned by (pt string comment '按天分区字段', seg string comment '时间跨度') -; \ No newline at end of file +CREATE TABLE tt_xx ( + f1 STRING, + f2 STRING COMMENT 'xxx', + f3 STRING COMMENT 'xxx', + gmt_first DATETIME COMMENT '首次时间', + gmt_last DATETIME COMMENT '末次时间', + pv_cnt BIGINT COMMENT 'PV数量', + pv_day_cnt BIGINT COMMENT 'PV天数' +) +COMMENT 'xx comment xxx' +PARTITIONED BY ( + pt STRING COMMENT '按天分区字段', + seg STRING COMMENT '时间跨度' +); \ No newline at end of file diff --git a/core/src/test/resources/bvt/parser/odps-9.txt b/core/src/test/resources/bvt/parser/odps-9.txt new file mode 100644 index 0000000000..2b1582cd2d --- /dev/null +++ b/core/src/test/resources/bvt/parser/odps-9.txt @@ -0,0 +1,8 @@ +select i, p, o + from qt + qualify row_number() over (partition by p order by o) = 1 + ; +--------------------------- +SELECT i, p, o +FROM qt +QUALIFY row_number() OVER (PARTITION BY p ORDER BY o) = 1; \ No newline at end of file