diff --git a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSQLObject.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSQLObject.java new file mode 100644 index 0000000000..b1fb5705f6 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSQLObject.java @@ -0,0 +1,9 @@ +package com.alibaba.druid.sql.dialect.postgresql.ast; + +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.druid.sql.dialect.postgresql.visitor.PGASTVisitor; + +public interface PGSQLObject extends SQLObject { + + void accept0(PGASTVisitor visitor); +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSQLObjectImpl.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSQLObjectImpl.java new file mode 100644 index 0000000000..2877683b81 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSQLObjectImpl.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2011 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.postgresql.ast; + +import com.alibaba.druid.sql.ast.SQLObjectImpl; +import com.alibaba.druid.sql.dialect.postgresql.visitor.PGASTVisitor; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +public abstract class PGSQLObjectImpl extends SQLObjectImpl implements PGSQLObject { + + private static final long serialVersionUID = 1L; + + public PGSQLObjectImpl(){ + + } + + @Override + protected void accept0(SQLASTVisitor visitor) { + this.accept0((PGASTVisitor) visitor); + } + + public abstract void accept0(PGASTVisitor visitor); +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSelectQueryBlock.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSelectQueryBlock.java new file mode 100644 index 0000000000..abe32fd974 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSelectQueryBlock.java @@ -0,0 +1,235 @@ +package com.alibaba.druid.sql.dialect.postgresql.ast; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.dialect.postgresql.visitor.PGASTVisitor; + +public class PGSelectQueryBlock extends SQLSelectQueryBlock { + + private static final long serialVersionUID = 1L; + + private WithClause with; + private List distinctOn = new ArrayList(2); + private SQLExpr limit; + private SQLExpr offset; + + private SQLOrderBy orderBy; + + public WithClause getWith() { + return with; + } + + public void setWith(WithClause with) { + this.with = with; + } + + public SQLExpr getLimit() { + return limit; + } + + public void setLimit(SQLExpr limit) { + this.limit = limit; + } + + public SQLOrderBy getOrderBy() { + return orderBy; + } + + public void setOrderBy(SQLOrderBy orderBy) { + this.orderBy = orderBy; + } + + public SQLExpr getOffset() { + return offset; + } + + public void setOffset(SQLExpr offset) { + this.offset = offset; + } + + public List getDistinctOn() { + return distinctOn; + } + + public void setDistinctOn(List distinctOn) { + this.distinctOn = distinctOn; + } + + public static class WindowClause extends PGSQLObjectImpl { + private static final long serialVersionUID = 1L; + private SQLExpr name; + private List definition = new ArrayList(2); + + public SQLExpr getName() { + return name; + } + + public void setName(SQLExpr name) { + this.name = name; + } + + public List getDefinition() { + return definition; + } + + public void setDefinition(List definition) { + this.definition = definition; + } + + @Override + public void accept0(PGASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, name); + acceptChild(visitor, definition); + } + } + } + + public static class FetchClause extends PGSQLObjectImpl { + private static final long serialVersionUID = 1L; + + public static enum Option { + FIRST, NEXT + } + + private Option option; + private SQLExpr count; + + public Option getOption() { + return option; + } + + public void setOption(Option option) { + this.option = option; + } + + public SQLExpr getCount() { + return count; + } + + public void setCount(SQLExpr count) { + this.count = count; + } + + @Override + public void accept0(PGASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, count); + } + } + + } + + public static class ForClause extends PGSQLObjectImpl { + private static final long serialVersionUID = 1L; + + public static enum Option { + UPDATE, SHARE + } + + private List of = new ArrayList(2); + private boolean noWait; + private Option option; + + public Option getOption() { + return option; + } + + public void setOption(Option option) { + this.option = option; + } + + public List getOf() { + return of; + } + + public void setOf(List of) { + this.of = of; + } + + public boolean isNoWait() { + return noWait; + } + + public void setNoWait(boolean noWait) { + this.noWait = noWait; + } + + @Override + public void accept0(PGASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, of); + } + } + } + + public static class WithClause extends PGSQLObjectImpl { + private static final long serialVersionUID = 1L; + private boolean recursive = false; + private List withQuery = new ArrayList(2); + + public boolean isRecursive() { + return recursive; + } + + public void setRecursive(boolean recursive) { + this.recursive = recursive; + } + + public List getWithQuery() { + return withQuery; + } + + public void setWithQuery(List withQuery) { + this.withQuery = withQuery; + } + + @Override + public void accept0(PGASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, withQuery); + } + } + } + + public static class WithQuery extends PGSQLObjectImpl { + private static final long serialVersionUID = 1L; + private SQLExpr name; + private final List columns = new ArrayList(); + private SQLSelectQuery subQuery; + + public SQLExpr getName() { + return name; + } + + public void setName(SQLExpr name) { + this.name = name; + } + + public SQLSelectQuery getSubQuery() { + return subQuery; + } + + public void setSubQuery(SQLSelectQuery subQuery) { + this.subQuery = subQuery; + } + + public List getColumns() { + return columns; + } + + @Override + public void accept0(PGASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, name); + acceptChild(visitor, columns); + acceptChild(visitor, subQuery); + } + } + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGLexer.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGLexer.java new file mode 100644 index 0000000000..1ee9d64803 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGLexer.java @@ -0,0 +1,105 @@ +package com.alibaba.druid.sql.dialect.postgresql.parser; + +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.druid.sql.parser.Keywords; +import com.alibaba.druid.sql.parser.Lexer; +import com.alibaba.druid.sql.parser.Token; + +public class PGLexer extends Lexer { + public final static Keywords DEFAULT_MYSQL_KEYWORDS; + + static { + Map map = new HashMap(); + map.put("EXISTS", Token.EXISTS); + map.put("THEN", Token.THEN); + map.put("AS", Token.AS); + map.put("GROUP", Token.GROUP); + map.put("BY", Token.BY); + map.put("HAVING", Token.HAVING); + map.put("DELETE", Token.DELETE); + map.put("ORDER", Token.ORDER); + map.put("INDEX", Token.INDEX); + map.put("FOR", Token.FOR); + map.put("SCHEMA", Token.SCHEMA); + map.put("FOREIGN", Token.FOREIGN); + map.put("REFERENCE", Token.REFERENCE); + map.put("REFERENCES", Token.REFERENCES); + map.put("CHECK", Token.CHECK); + map.put("PRIMARY", Token.PRIMARY); + map.put("KEY", Token.KEY); + map.put("CONSTRAINT", Token.CONSTRAINT); + map.put("DEFAULT", Token.DEFAULT); + map.put("VIEW", Token.VIEW); + map.put("CREATE", Token.CREATE); + map.put("VALUES", Token.VALUES); + map.put("ALTER", Token.ALTER); + map.put("TABLE", Token.TABLE); + map.put("DROP", Token.DROP); + map.put("SET", Token.SET); + map.put("INTO", Token.INTO); + map.put("UPDATE", Token.UPDATE); + map.put("NULL", Token.NULL); + map.put("IS", Token.IS); + map.put("NOT", Token.NOT); + map.put("SELECT", Token.SELECT); + map.put("INSERT", Token.INSERT); + map.put("FROM", Token.FROM); + map.put("WHERE", Token.WHERE); + map.put("AND", Token.AND); + map.put("OR", Token.OR); + map.put("XOR", Token.XOR); + map.put("DISTINCT", Token.DISTINCT); + map.put("UNIQUE", Token.UNIQUE); + map.put("ALL", Token.ALL); + map.put("UNION", Token.UNION); + map.put("INTERSECT", Token.INTERSECT); + map.put("MINUS", Token.MINUS); + map.put("INNER", Token.INNER); + map.put("LEFT", Token.LEFT); + map.put("RIGHT", Token.RIGHT); + map.put("FULL", Token.FULL); + map.put("ON", Token.ON); + map.put("OUTER", Token.OUTER); + map.put("JOIN", Token.JOIN); + map.put("NEW", Token.NEW); + map.put("CASE", Token.CASE); + map.put("WHEN", Token.WHEN); + map.put("END", Token.END); + map.put("WHEN", Token.WHEN); + map.put("ELSE", Token.ELSE); + map.put("EXISTS", Token.EXISTS); + map.put("CAST", Token.CAST); + map.put("IN", Token.IN); + map.put("ASC", Token.ASC); + map.put("DESC", Token.DESC); + map.put("LIKE", Token.LIKE); + map.put("ESCAPE", Token.ESCAPE); + map.put("BETWEEN", Token.BETWEEN); + map.put("INTERVAL", Token.INTERVAL); + map.put("LOCK", Token.LOCK); + map.put("SOME", Token.SOME); + map.put("ANY", Token.ANY); + + map.put("LIMIT", Token.LIMIT); + map.put("OFFSET", Token.OFFSET); + map.put("WINDOW", Token.WINDOW); + map.put("ROW", Token.ROW); + map.put("ROWS", Token.ROWS); + map.put("ONLY", Token.ONLY); + map.put("FIRST", Token.FIRST); + map.put("NEXT", Token.NEXT); + map.put("FETCH", Token.FETCH); + map.put("OF", Token.OF); + map.put("SHARE", Token.SHARE); + map.put("NOWAIT", Token.NOWAIT); + map.put("RECURSIVE", Token.RECURSIVE); + + DEFAULT_MYSQL_KEYWORDS = new Keywords(map); + } + public PGLexer(String input){ + super(input); + super.keywods = DEFAULT_MYSQL_KEYWORDS; + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGSQLStatementParser.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGSQLStatementParser.java new file mode 100644 index 0000000000..cbab81f3a0 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGSQLStatementParser.java @@ -0,0 +1,20 @@ +package com.alibaba.druid.sql.dialect.postgresql.parser; + +import com.alibaba.druid.sql.parser.Lexer; +import com.alibaba.druid.sql.parser.ParserException; +import com.alibaba.druid.sql.parser.SQLStatementParser; + +public class PGSQLStatementParser extends SQLStatementParser { + public PGSQLStatementParser(String sql) throws ParserException { + this(new PGLexer(sql)); + this.lexer.nextToken(); + } + + public PGSQLStatementParser(Lexer lexer) { + super(lexer); + } + + public PGSelectParser createSQLSelectParser() { + return new PGSelectParser(this.lexer); + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGSelectParser.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGSelectParser.java new file mode 100644 index 0000000000..9e32091f95 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGSelectParser.java @@ -0,0 +1,220 @@ +package com.alibaba.druid.sql.dialect.postgresql.parser; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLSetQuantifier; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.statement.SQLSelectQuery; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.WithQuery; +import com.alibaba.druid.sql.parser.Lexer; +import com.alibaba.druid.sql.parser.ParserException; +import com.alibaba.druid.sql.parser.SQLSelectParser; +import com.alibaba.druid.sql.parser.Token; + +public class PGSelectParser extends SQLSelectParser { + + public PGSelectParser(Lexer lexer) { + super(lexer); + } + + public PGSelectParser(String sql) throws ParserException { + this(new PGLexer(sql)); + this.lexer.nextToken(); + } + + @Override + protected SQLSelectQuery query() throws ParserException { + PGSelectQueryBlock queryBlock = new PGSelectQueryBlock(); + + if (lexer.token() == Token.WITH) { + lexer.nextToken(); + + PGSelectQueryBlock.WithClause withClause = new PGSelectQueryBlock.WithClause(); + + if (lexer.token() == Token.RECURSIVE) { + lexer.nextToken(); + withClause.setRecursive(true); + } + + for (;;) { + WithQuery withQuery = withQuery(); + withClause.getWithQuery().add(withQuery); + if (lexer.token() == Token.COMMA) { + lexer.nextToken(); + continue; + } else { + break; + } + } + + queryBlock.setWith(withClause); + } + + accept(Token.SELECT); + + if (lexer.token() == Token.DISTINCT) { + queryBlock.setDistionOption(SQLSetQuantifier.DISTINCT); + lexer.nextToken(); + + if (lexer.token() == Token.ON) { + lexer.nextToken(); + + for (;;) { + SQLExpr expr = this.createExprParser().expr(); + queryBlock.getDistinctOn().add(expr); + if (lexer.token() == Token.COMMA) { + lexer.nextToken(); + continue; + } else { + break; + } + } + } + } else if (lexer.token() == Token.ALL) { + queryBlock.setDistionOption(SQLSetQuantifier.ALL); + lexer.nextToken(); + } + + parseFrom(queryBlock); + + parseWhere(queryBlock); + + parseGroupBy(queryBlock); + + if (lexer.token() == Token.WINDOW) { + lexer.nextToken(); + PGSelectQueryBlock.WindowClause window = new PGSelectQueryBlock.WindowClause(); + window.setName(this.expr()); + accept(Token.AS); + + for (;;) { + SQLExpr expr = this.createExprParser().expr(); + window.getDefinition().add(expr); + if (lexer.token() == Token.COMMA) { + lexer.nextToken(); + continue; + } else { + break; + } + } + } + + queryBlock.setOrderBy(this.createExprParser().parseOrderBy()); + + if (lexer.token() == Token.LIMIT) { + lexer.nextToken(); + if (lexer.token() == Token.ALL) { + queryBlock.setLimit(new SQLIdentifierExpr("ALL")); + lexer.nextToken(); + } else { + SQLExpr limit = expr(); + queryBlock.setLimit(limit); + } + } + + if (lexer.token() == Token.OFFSET) { + lexer.nextToken(); + SQLExpr offset = expr(); + queryBlock.setOffset(offset); + + if (lexer.token() == Token.ROW || lexer.token() == Token.ROWS) { + lexer.nextToken(); + } else { + throw new ParserException("expect 'ROW' or 'ROWS'"); + } + } + + if (lexer.token() == Token.FETCH) { + lexer.nextToken(); + PGSelectQueryBlock.FetchClause fetch = new PGSelectQueryBlock.FetchClause(); + + if (lexer.token() == Token.FIRST) { + fetch.setOption(PGSelectQueryBlock.FetchClause.Option.FIRST); + } else if (lexer.token() == Token.NEXT) { + fetch.setOption(PGSelectQueryBlock.FetchClause.Option.NEXT); + } else { + throw new ParserException("expect 'FIRST' or 'NEXT'"); + } + + SQLExpr count = expr(); + fetch.setCount(count); + + if (lexer.token() == Token.ROW || lexer.token() == Token.ROWS) { + lexer.nextToken(); + } else { + throw new ParserException("expect 'ROW' or 'ROWS'"); + } + + if (lexer.token() == Token.ONLY) { + lexer.nextToken(); + } else { + throw new ParserException("expect 'ONLY'"); + } + } + + if (lexer.token() == Token.FOR) { + lexer.nextToken(); + + PGSelectQueryBlock.ForClause forClause = new PGSelectQueryBlock.ForClause(); + + if (lexer.token() == Token.UPDATE) { + forClause.setOption(PGSelectQueryBlock.ForClause.Option.UPDATE); + } else if (lexer.token() == Token.SHARE) { + forClause.setOption(PGSelectQueryBlock.ForClause.Option.SHARE); + } else { + throw new ParserException("expect 'FIRST' or 'NEXT'"); + } + + accept(Token.OF); + + for (;;) { + SQLExpr expr = this.createExprParser().expr(); + forClause.getOf().add(expr); + if (lexer.token() == Token.COMMA) { + lexer.nextToken(); + continue; + } else { + break; + } + } + + if (lexer.token() == Token.NOWAIT) { + lexer.nextToken(); + forClause.setNoWait(true); + } + } + + return queryBlock; + } + + private WithQuery withQuery() { + WithQuery withQuery = new WithQuery(); + withQuery.setName(expr()); + if (lexer.token() == Token.LPAREN) { + lexer.nextToken(); + + for (;;) { + SQLExpr expr = this.createExprParser().expr(); + withQuery.getColumns().add(expr); + if (lexer.token() == Token.COMMA) { + lexer.nextToken(); + continue; + } else { + break; + } + } + + accept(Token.RPAREN); + } + + accept(Token.AS); + + if (lexer.token() == Token.LPAREN) { + SQLSelectQuery subQuery = query(); + withQuery.setSubQuery(subQuery); + accept(Token.RPAREN); + } + + return withQuery; + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGASTVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGASTVisitor.java new file mode 100644 index 0000000000..85c3836945 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGASTVisitor.java @@ -0,0 +1,26 @@ +package com.alibaba.druid.sql.dialect.postgresql.visitor; + +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +public interface PGASTVisitor extends SQLASTVisitor { + void endVisit(PGSelectQueryBlock.WindowClause x); + + boolean visit(PGSelectQueryBlock.WindowClause x); + + void endVisit(PGSelectQueryBlock.FetchClause x); + + boolean visit(PGSelectQueryBlock.FetchClause x); + + void endVisit(PGSelectQueryBlock.ForClause x); + + boolean visit(PGSelectQueryBlock.ForClause x); + + void endVisit(PGSelectQueryBlock.WithQuery x); + + boolean visit(PGSelectQueryBlock.WithQuery x); + + void endVisit(PGSelectQueryBlock.WithClause x); + + boolean visit(PGSelectQueryBlock.WithClause x); +} diff --git a/src/main/java/com/alibaba/druid/sql/parser/Token.java b/src/main/java/com/alibaba/druid/sql/parser/Token.java index 9e890aa604..527313c51e 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/Token.java +++ b/src/main/java/com/alibaba/druid/sql/parser/Token.java @@ -99,6 +99,20 @@ public enum Token { LIMIT("LIMIT"), KILL("KILL"), + //postgresql + WINDOW("WINDOW"), + OFFSET("OFFSET"), + ROW("ROW"), + ROWS("ROWS"), + ONLY("ONLY"), + FIRST("FIRST"), + NEXT("NEXT"), + FETCH("FETCH"), + OF("OF"), + SHARE("SHARE"), + NOWAIT("NOWAIT"), + RECURSIVE("RECURSIVE"), + // oracle START("START"), PRIOR("PRIOR"),