From b29a9bcacb57e1e8eb0c80b1ba1b4edd300df14a Mon Sep 17 00:00:00 2001 From: KANLON Date: Tue, 8 Feb 2022 20:27:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=A4=9A=E6=94=AF?= =?UTF-8?q?=E6=8C=81presto=E5=88=86=E9=A1=B5=E7=9A=84=E8=AF=AD=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alibaba/druid/sql/SQLUtils.java | 3 + .../ast/stmt/PrestoSelectStatement.java | 56 +++++ .../presto/parser/PrestoExprParser.java | 10 +- .../presto/parser/PrestoSelectParser.java | 202 ++++++++++++++++++ .../presto/parser/PrestoStatementParser.java | 15 +- .../presto/visitor/PrestoOutputVisitor.java | 47 ++++ .../ParameterizedOutputVisitorUtils.java | 5 +- 7 files changed, 329 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/presto/ast/stmt/PrestoSelectStatement.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoSelectParser.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/presto/visitor/PrestoOutputVisitor.java diff --git a/src/main/java/com/alibaba/druid/sql/SQLUtils.java b/src/main/java/com/alibaba/druid/sql/SQLUtils.java index 8c06ad9de9..32a47af2c1 100644 --- a/src/main/java/com/alibaba/druid/sql/SQLUtils.java +++ b/src/main/java/com/alibaba/druid/sql/SQLUtils.java @@ -43,6 +43,7 @@ import com.alibaba.druid.sql.dialect.oracle.visitor.OracleSchemaStatVisitor; import com.alibaba.druid.sql.dialect.oracle.visitor.OracleToMySqlOutputVisitor; import com.alibaba.druid.sql.dialect.postgresql.visitor.PGOutputVisitor; import com.alibaba.druid.sql.dialect.postgresql.visitor.PGSchemaStatVisitor; +import com.alibaba.druid.sql.dialect.presto.visitor.PrestoOutputVisitor; import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerOutputVisitor; import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerSchemaStatVisitor; import com.alibaba.druid.sql.parser.*; @@ -499,6 +500,8 @@ public class SQLUtils { return new BlinkOutputVisitor(out); case antspark: return new AntsparkOutputVisitor(out); + case presto: + return new PrestoOutputVisitor(out); case clickhouse: return new ClickhouseOutputVisitor(out); default: diff --git a/src/main/java/com/alibaba/druid/sql/dialect/presto/ast/stmt/PrestoSelectStatement.java b/src/main/java/com/alibaba/druid/sql/dialect/presto/ast/stmt/PrestoSelectStatement.java new file mode 100644 index 0000000000..b1d69c675f --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/presto/ast/stmt/PrestoSelectStatement.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2017 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.presto.ast.stmt; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.dialect.presto.visitor.PrestoVisitor; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +/** + * presto 的 select语句 + * + * @author zhangcanlong + * @date 2022/01/11 + */ +public class PrestoSelectStatement extends SQLSelectStatement implements SQLStatement { + + public PrestoSelectStatement() { + super(DbType.postgresql); + } + + public PrestoSelectStatement(SQLSelect select) { + super(select, DbType.postgresql); + } + + @Override + protected void accept0(SQLASTVisitor visitor) { + if (visitor instanceof PrestoVisitor) { + this.accept0((PrestoVisitor) visitor); + } else { + super.accept0(visitor); + } + } + + public void accept0(PrestoVisitor visitor) { + if (visitor.visit(this)) { + this.acceptChild(visitor, this.select); + } + visitor.endVisit(this); + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoExprParser.java b/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoExprParser.java index e9341e44f1..47a6e0d688 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoExprParser.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoExprParser.java @@ -16,13 +16,9 @@ package com.alibaba.druid.sql.dialect.presto.parser; import com.alibaba.druid.DbType; -import com.alibaba.druid.sql.ast.SQLExpr; -import com.alibaba.druid.sql.ast.expr.SQLCharExpr; -import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; -import com.alibaba.druid.sql.ast.expr.SQLIntervalExpr; -import com.alibaba.druid.sql.ast.expr.SQLIntervalUnit; -import com.alibaba.druid.sql.dialect.phoenix.parser.PhoenixLexer; -import com.alibaba.druid.sql.parser.*; +import com.alibaba.druid.sql.parser.Lexer; +import com.alibaba.druid.sql.parser.SQLExprParser; +import com.alibaba.druid.sql.parser.SQLParserFeature; /** * Created by wenshao on 16/9/13. diff --git a/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoSelectParser.java b/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoSelectParser.java new file mode 100644 index 0000000000..c4ad027e67 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoSelectParser.java @@ -0,0 +1,202 @@ +/* + * Copyright 1999-2017 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.presto.parser; + +import com.alibaba.druid.DbType; +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; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.parser.SQLExprParser; +import com.alibaba.druid.sql.parser.SQLSelectListCache; +import com.alibaba.druid.sql.parser.SQLSelectParser; +import com.alibaba.druid.sql.parser.Token; +import com.alibaba.druid.util.FnvHash; + +/** + * presto的 选择解析器 + * + * @author zhangcanlong + * @date 2022/01/11 + */ +public class PrestoSelectParser extends SQLSelectParser { + + public PrestoSelectParser(SQLExprParser exprParser) { + super(exprParser); + } + + public PrestoSelectParser(SQLExprParser exprParser, SQLSelectListCache selectListCache) { + super(exprParser, selectListCache); + } + + public PrestoSelectParser(String sql) { + this(new PrestoExprParser(sql)); + } + + protected SQLExprParser createExprParser() { + return new PrestoExprParser(this.lexer); + } + + @Override + public SQLSelectQuery query(SQLObject parent, boolean acceptUnion) { + if (this.lexer.token() == Token.LPAREN) { + this.lexer.nextToken(); + + SQLSelectQuery select = this.query(); + this.accept(Token.RPAREN); + + return this.queryRest(select, acceptUnion); + } + + if (this.lexer.token() == Token.VALUES) { + return this.valuesQuery(acceptUnion); + } + + SQLSelectQueryBlock queryBlock = new SQLSelectQueryBlock(this.dbType); + + if (this.lexer.hasComment() && this.lexer.isKeepComments()) { + queryBlock.addBeforeComment(this.lexer.readAndResetComments()); + } + + this.accept(Token.SELECT); + + if (this.lexer.token() == Token.HINT) { + this.exprParser.parseHints(queryBlock.getHints()); + } + + if (this.lexer.token() == Token.COMMENT) { + this.lexer.nextToken(); + } + + if (DbType.informix == this.dbType) { + if (this.lexer.identifierEquals(FnvHash.Constants.SKIP)) { + this.lexer.nextToken(); + SQLExpr offset = this.exprParser.primary(); + queryBlock.setOffset(offset); + } + + if (this.lexer.identifierEquals(FnvHash.Constants.FIRST)) { + this.lexer.nextToken(); + SQLExpr first = this.exprParser.primary(); + queryBlock.setFirst(first); + } + } + + if (this.lexer.token() == Token.DISTINCT) { + queryBlock.setDistionOption(SQLSetQuantifier.DISTINCT); + this.lexer.nextToken(); + } else if (this.lexer.token() == Token.UNIQUE) { + queryBlock.setDistionOption(SQLSetQuantifier.UNIQUE); + this.lexer.nextToken(); + } else if (this.lexer.token() == Token.ALL) { + queryBlock.setDistionOption(SQLSetQuantifier.ALL); + this.lexer.nextToken(); + } + + this.parseSelectList(queryBlock); + + if (this.lexer.token() == Token.INTO) { + this.lexer.nextToken(); + + SQLExpr expr = this.expr(); + if (this.lexer.token() != Token.COMMA) { + queryBlock.setInto(expr); + } + } + + this.parseFrom(queryBlock); + + this.parseWhere(queryBlock); + + this.parseGroupBy(queryBlock); + + if (this.lexer.identifierEquals(FnvHash.Constants.WINDOW)) { + this.parseWindow(queryBlock); + } + + this.parseSortBy(queryBlock); + + this.parseFetchClause(queryBlock); + + if (this.lexer.token() == Token.FOR) { + this.lexer.nextToken(); + this.accept(Token.UPDATE); + + queryBlock.setForUpdate(true); + + if (this.lexer.identifierEquals(FnvHash.Constants.NO_WAIT) || this.lexer.identifierEquals(FnvHash.Constants.NOWAIT)) { + this.lexer.nextToken(); + queryBlock.setNoWait(true); + } else if (this.lexer.identifierEquals(FnvHash.Constants.WAIT)) { + this.lexer.nextToken(); + SQLExpr waitTime = this.exprParser.primary(); + queryBlock.setWaitTime(waitTime); + } + } + + return this.queryRest(queryBlock, acceptUnion); + } + + + @Override + public void parseFetchClause(SQLSelectQueryBlock queryBlock) { + + // 如果是presto,则先解析 offset 再解析limit + if (this.lexer.identifierEquals(FnvHash.Constants.OFFSET) || this.lexer.token() == Token.OFFSET) { + this.lexer.nextToken(); + SQLExpr offset = this.exprParser.expr(); + queryBlock.setOffset(offset); + if (this.lexer.identifierEquals(FnvHash.Constants.ROW) || this.lexer.identifierEquals(FnvHash.Constants.ROWS)) { + this.lexer.nextToken(); + } + } + + if (this.lexer.token() == Token.LIMIT) { + SQLLimit limit = queryBlock.getLimit(); + // 原始的limit + SQLLimit originLimit = this.exprParser.parseLimit(); + if (limit == null) { + limit = originLimit; + } + limit.setRowCount(originLimit.getRowCount()); + queryBlock.setLimit(limit); + return; + } + + + if (this.lexer.token() == Token.FETCH) { + this.lexer.nextToken(); + if (this.lexer.token() == Token.FIRST || this.lexer.token() == Token.NEXT || this.lexer.identifierEquals(FnvHash.Constants.NEXT)) { + this.lexer.nextToken(); + } else { + this.acceptIdentifier("FIRST"); + } + SQLExpr first = this.exprParser.primary(); + queryBlock.setFirst(first); + if (this.lexer.identifierEquals(FnvHash.Constants.ROW) || this.lexer.identifierEquals(FnvHash.Constants.ROWS)) { + this.lexer.nextToken(); + } + + if (this.lexer.token() == Token.ONLY) { + this.lexer.nextToken(); + } else { + this.acceptIdentifier("ONLY"); + } + } + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoStatementParser.java b/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoStatementParser.java index d9a925d7f1..3bebcd9802 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoStatementParser.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/presto/parser/PrestoStatementParser.java @@ -18,7 +18,8 @@ package com.alibaba.druid.sql.dialect.presto.parser; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLName; import com.alibaba.druid.sql.ast.statement.SQLInsertInto; -import com.alibaba.druid.sql.dialect.phoenix.parser.PhoenixExprParser; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGSelectStatement; import com.alibaba.druid.sql.parser.Lexer; import com.alibaba.druid.sql.parser.SQLStatementParser; import com.alibaba.druid.sql.parser.Token; @@ -35,6 +36,18 @@ public class PrestoStatementParser extends SQLStatementParser { super(new PrestoExprParser(lexer)); } + @Override + public PrestoSelectParser createSQLSelectParser() { + return new PrestoSelectParser(this.exprParser, selectListCache); + } + + @Override + public PGSelectStatement parseSelect() { + PrestoSelectParser selectParser = createSQLSelectParser(); + SQLSelect select = selectParser.select(); + return new PGSelectStatement(select); + } + @Override protected void parseInsertColumns(SQLInsertInto insert) { if (lexer.token() == Token.RPAREN ) { diff --git a/src/main/java/com/alibaba/druid/sql/dialect/presto/visitor/PrestoOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/presto/visitor/PrestoOutputVisitor.java new file mode 100644 index 0000000000..aed08e1c35 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/presto/visitor/PrestoOutputVisitor.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2017 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.presto.visitor; + +import com.alibaba.druid.sql.ast.SQLLimit; +import com.alibaba.druid.sql.dialect.phoenix.visitor.PhoenixASTVisitor; +import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; + +/** + * presto 的输出的视图信息 + * + * @author zhangcanlong + * @since 2022-01-07 + */ +public class PrestoOutputVisitor extends SQLASTOutputVisitor implements PhoenixASTVisitor { + public PrestoOutputVisitor(Appendable appender) { + super(appender); + } + + public PrestoOutputVisitor(Appendable appender, boolean parameterized) { + super(appender, parameterized); + } + + @Override + public boolean visit(SQLLimit x) { + if (x.getOffset() != null) { + this.print0(this.ucase ? " OFFSET " : " offset "); + x.getOffset().accept(this); + } + this.print0(this.ucase ? " LIMIT " : " limit "); + x.getRowCount().accept(this); + return false; + } +} diff --git a/src/main/java/com/alibaba/druid/sql/visitor/ParameterizedOutputVisitorUtils.java b/src/main/java/com/alibaba/druid/sql/visitor/ParameterizedOutputVisitorUtils.java index 22fba795b9..bbc6094750 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/ParameterizedOutputVisitorUtils.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/ParameterizedOutputVisitorUtils.java @@ -34,6 +34,7 @@ import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTParameterizedVisito import com.alibaba.druid.sql.dialect.oracle.visitor.OracleParameterizedOutputVisitor; import com.alibaba.druid.sql.dialect.phoenix.visitor.PhoenixOutputVisitor; import com.alibaba.druid.sql.dialect.postgresql.visitor.PGOutputVisitor; +import com.alibaba.druid.sql.dialect.presto.visitor.PrestoOutputVisitor; import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerOutputVisitor; import com.alibaba.druid.sql.parser.SQLParserFeature; import com.alibaba.druid.sql.parser.SQLParserUtils; @@ -78,7 +79,7 @@ public class ParameterizedOutputVisitorUtils { return parameterize(sql, dbType, null, null); } - public static String parameterize(String sql, DbType dbType, VisitorFeature ...features) { + public static String parameterize(String sql, DbType dbType, VisitorFeature...features) { return parameterize(sql, dbType, null, features); } @@ -408,6 +409,8 @@ public class ParameterizedOutputVisitorUtils { return new DB2OutputVisitor(out, true); case phoenix: return new PhoenixOutputVisitor(out, true); + case presto: + return new PrestoOutputVisitor(out, true); default: return new SQLASTOutputVisitor(out, true); }