From 7df038df4ddf17c8f4986703f7aa6837232d6b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A9=E5=B0=91?= Date: Wed, 22 Feb 2012 10:02:27 +0800 Subject: [PATCH] add postgresql parser support --- .../mysql/visitor/MySqlSchemaStatVisitor.java | 751 ++++++++++-------- .../dialect/postgresql/ast/PGSQLObject.java | 9 + .../postgresql/ast/PGSQLObjectImpl.java | 36 + .../postgresql/ast/PGSelectQueryBlock.java | 286 +++++++ .../dialect/postgresql/parser/PGLexer.java | 106 +++ .../parser/PGSQLStatementParser.java | 34 + .../postgresql/parser/PGSelectParser.java | 229 ++++++ .../postgresql/visitor/PGASTVisitor.java | 26 + .../visitor/PGASTVisitorAdapter.java | 68 ++ .../postgresql/visitor/PGOutputVisitor.java | 213 +++++ .../visitor/PGSchemaStatVisitor.java | 66 ++ .../com/alibaba/druid/sql/parser/Token.java | 14 + .../druid/sql/visitor/SchemaStatVisitor.java | 283 +++++++ .../postgresql/PostgresqlResourceTest.java | 83 ++ .../resources/bvt/parser/postgresql-0.txt | 19 + 15 files changed, 1871 insertions(+), 352 deletions(-) create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSQLObject.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSQLObjectImpl.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSelectQueryBlock.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGLexer.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGSQLStatementParser.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGSelectParser.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGASTVisitor.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGASTVisitorAdapter.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGOutputVisitor.java create mode 100644 src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGSchemaStatVisitor.java create mode 100644 src/main/java/com/alibaba/druid/sql/visitor/SchemaStatVisitor.java create mode 100644 src/test/java/com/alibaba/druid/bvt/sql/postgresql/PostgresqlResourceTest.java create mode 100644 src/test/resources/bvt/parser/postgresql-0.txt diff --git a/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlSchemaStatVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlSchemaStatVisitor.java index 91d3ea5f11..8a3082c4af 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlSchemaStatVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlSchemaStatVisitor.java @@ -1,366 +1,413 @@ package com.alibaba.druid.sql.dialect.mysql.visitor; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Set; import com.alibaba.druid.sql.ast.SQLName; -import com.alibaba.druid.sql.ast.SQLObject; -import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; -import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; -import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; -import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; -import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; -import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; -import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; -import com.alibaba.druid.sql.ast.statement.SQLSelect; -import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; -import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; -import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource; -import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.MySqlKey; +import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlBinaryExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlBooleanExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlExtractExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlIntervalExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlMatchAgainstExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.CobarShowStatus; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlBinlogStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCommitStatement; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlExecuteStatement; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlKillStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlLoadDataInFileStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlLoadXmlStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlPrepareStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlReplicateStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlResetStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRollbackStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectGroupBy; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock.Limit; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlShowColumnsStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlShowDatabasesStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlShowStatusStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlShowTablesStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlShowWarningsStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlStartTransactionStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlTableIndex; +import com.alibaba.druid.sql.visitor.SchemaStatVisitor; import com.alibaba.druid.stat.TableStat; -import com.alibaba.druid.stat.TableStat.Column; import com.alibaba.druid.stat.TableStat.Mode; -public class MySqlSchemaStatVisitor extends MySqlASTVisitorAdapter { - - private HashMap tableStats = new HashMap(); - private Set fields = new HashSet(); - - private final static ThreadLocal> aliasLocal = new ThreadLocal>(); - private final static ThreadLocal currentTableLocal = new ThreadLocal(); - private final static ThreadLocal modeLocal = new ThreadLocal(); - - public boolean visit(SQLSubqueryTableSource x) { - Map aliasMap = aliasLocal.get(); - if (aliasMap != null) { - if (x.getAlias() != null) { - aliasMap.put(x.getAlias(), null); - } - } - return true; - } - - public boolean visit(SQLExprTableSource x) { - if (x.getExpr() instanceof SQLIdentifierExpr || x.getExpr() instanceof SQLPropertyExpr) { - String ident = x.getExpr().toString(); - TableStat stat = tableStats.get(ident); - if (stat == null) { - stat = new TableStat(); - tableStats.put(new TableStat.Name(ident), stat); - } - - Mode mode = modeLocal.get(); - switch (mode) { - case Delete: - stat.incrementDeleteCount(); - break; - case Insert: - stat.incrementInsertCount(); - break; - case Update: - stat.incrementUpdateCount(); - break; - case Select: - stat.incrementSelectCount(); - break; - default: - break; - } - - Map aliasMap = aliasLocal.get(); - if (aliasMap != null) { - if (x.getAlias() != null) { - aliasMap.put(x.getAlias(), ident); - } - aliasMap.put(ident, ident); - } - } else { - accept(x.getExpr()); - } - - return false; - } - - public boolean visit(SQLSelect x) { - accept(x.getQuery()); - - String originalTable = currentTableLocal.get(); - - currentTableLocal.set((String) x.getQuery().getAttribute("table")); - x.putAttribute("_old_local_", originalTable); - - accept(x.getOrderBy()); - - currentTableLocal.set(originalTable); - - return false; - } - - public boolean visit(SQLAggregateExpr x) { - accept(x.getArguments()); - return false; - } - - public boolean visit(SQLMethodInvokeExpr x) { - accept(x.getParameters()); - return false; - } - - public boolean visit(SQLUpdateStatement x) { - aliasLocal.set(new HashMap()); - - String ident = x.getTableName().toString(); - currentTableLocal.set(ident); - - TableStat stat = tableStats.get(ident); - if (stat == null) { - stat = new TableStat(); - tableStats.put(new TableStat.Name(ident), stat); - } - stat.incrementUpdateCount(); - - Map aliasMap = aliasLocal.get(); - aliasMap.put(ident, ident); - - accept(x.getItems()); - accept(x.getWhere()); - - return false; - } - - public void endVisit(SQLUpdateStatement x) { - aliasLocal.set(null); - } - - public boolean visit(MySqlDeleteStatement x) { - aliasLocal.set(new HashMap()); - - x.putAttribute("_original_use_mode", modeLocal.get()); - modeLocal.set(Mode.Delete); - - aliasLocal.set(new HashMap()); - - if (x.getTableNames().size() == 1) { - String ident = ((SQLIdentifierExpr) x.getTableNames().get(0)).getName(); - currentTableLocal.set(ident); - } - - for (SQLName tableName : x.getTableNames()) { - String ident = tableName.toString(); - TableStat stat = tableStats.get(ident); - if (stat == null) { - stat = new TableStat(); - tableStats.put(new TableStat.Name(ident), stat); - } - stat.incrementDeleteCount(); - } - - accept(x.getWhere()); - accept(x.getFrom()); - accept(x.getUsing()); - accept(x.getOrderBy()); - accept(x.getLimit()); - - return false; - } - - public void endVisit(MySqlDeleteStatement x) { - aliasLocal.set(null); - } - - public boolean visit(SQLSelectStatement x) { - aliasLocal.set(new HashMap()); - return true; - } - - public void endVisit(SQLSelectStatement x) { - aliasLocal.set(null); - } - - @Override - public void endVisit(MySqlInsertStatement x) { - Mode originalMode = (Mode) x.getAttribute("_original_use_mode"); - modeLocal.set(originalMode); - } - - @Override - public boolean visit(MySqlInsertStatement x) { - x.putAttribute("_original_use_mode", modeLocal.get()); - modeLocal.set(Mode.Insert); - - aliasLocal.set(new HashMap()); - - String originalTable = currentTableLocal.get(); - - if (x.getTableName() instanceof SQLIdentifierExpr) { - String ident = ((SQLIdentifierExpr) x.getTableName()).getName(); - currentTableLocal.set(ident); - x.putAttribute("_old_local_", originalTable); - - TableStat stat = tableStats.get(ident); - if (stat == null) { - stat = new TableStat(); - tableStats.put(new TableStat.Name(ident), stat); - } - stat.incrementInsertCount(); - - Map aliasMap = aliasLocal.get(); - if (aliasMap != null) { - if (x.getAlias() != null) { - aliasMap.put(x.getAlias(), ident); - } - aliasMap.put(ident, ident); - } - } - - accept(x.getColumns()); - accept(x.getValuesList()); - accept(x.getQuery()); - accept(x.getDuplicateKeyUpdate()); - - return false; - } - - @Override - public boolean visit(SQLInsertStatement x) { - x.putAttribute("_original_use_mode", modeLocal.get()); - modeLocal.set(Mode.Insert); - - aliasLocal.set(new HashMap()); - - String originalTable = currentTableLocal.get(); - - if (x.getTableName() instanceof SQLIdentifierExpr) { - String ident = ((SQLIdentifierExpr) x.getTableName()).getName(); - currentTableLocal.set(ident); - x.putAttribute("_old_local_", originalTable); - - TableStat stat = tableStats.get(ident); - if (stat == null) { - stat = new TableStat(); - tableStats.put(new TableStat.Name(ident), stat); - } - stat.incrementInsertCount(); - - Map aliasMap = aliasLocal.get(); - if (aliasMap != null) { - if (x.getAlias() != null) { - aliasMap.put(x.getAlias(), ident); - } - aliasMap.put(ident, ident); - } - } - - accept(x.getColumns()); - accept(x.getQuery()); - - return false; - } - - protected void accept(SQLObject x) { - if (x != null) { - x.accept(this); - } - } - - protected void accept(List nodes) { - for (int i = 0, size = nodes.size(); i < size; ++i) { - accept(nodes.get(i)); - } - } - - public boolean visit(SQLSelectQueryBlock x) { - - if (x.getFrom() instanceof SQLSubqueryTableSource) { - x.getFrom().accept(this); - return false; - } - - x.putAttribute("_original_use_mode", modeLocal.get()); - modeLocal.set(Mode.Select); - - String originalTable = currentTableLocal.get(); - - if (x.getFrom() instanceof SQLExprTableSource) { - SQLExprTableSource tableSource = (SQLExprTableSource) x.getFrom(); - if (tableSource.getExpr() instanceof SQLIdentifierExpr) { - String ident = ((SQLIdentifierExpr) tableSource.getExpr()).getName(); - currentTableLocal.set(ident); - x.putAttribute("_old_local_", originalTable); - } - } - - x.getFrom().accept(this); // 提前执行,获得aliasMap - - return true; - } - - public void endVisit(SQLSelectQueryBlock x) { - String originalTable = (String) x.getAttribute("_old_local_"); - x.putAttribute("table", currentTableLocal.get()); - currentTableLocal.set(originalTable); - - Mode originalMode = (Mode) x.getAttribute("_original_use_mode"); - modeLocal.set(originalMode); - } - - public boolean visit(SQLJoinTableSource x) { - return true; - } - - public boolean visit(SQLPropertyExpr x) { - if (x.getOwner() instanceof SQLIdentifierExpr) { - String owner = ((SQLIdentifierExpr) x.getOwner()).getName(); - - if (owner != null) { - Map aliasMap = aliasLocal.get(); - if (aliasMap != null) { - String table = aliasMap.get(owner); - - // table == null时是SubQuery - if (table != null) { - fields.add(new Column(table, x.getName())); - } - } - } - } - return false; - } - - public boolean visit(SQLIdentifierExpr x) { - String currentTable = currentTableLocal.get(); - - if (currentTable != null) { - fields.add(new Column(currentTable, x.getName())); - } - return false; - } - - public boolean visit(SQLAllColumnExpr x) { - String currentTable = currentTableLocal.get(); - - if (currentTable != null) { - fields.add(new Column(currentTable, "*")); - } - return false; - } - - public Map getTables() { - return tableStats; - } - - public boolean containsTable(String tableName) { - return tableStats.containsKey(new TableStat.Name(tableName)); - } - - public Set getFields() { - return fields; - } +public class MySqlSchemaStatVisitor extends SchemaStatVisitor implements + MySqlASTVisitor { + + public boolean visit(MySqlDeleteStatement x) { + aliasLocal.set(new HashMap()); + + x.putAttribute("_original_use_mode", modeLocal.get()); + modeLocal.set(Mode.Delete); + + aliasLocal.set(new HashMap()); + + if (x.getTableNames().size() == 1) { + String ident = ((SQLIdentifierExpr) x.getTableNames().get(0)) + .getName(); + currentTableLocal.set(ident); + } + + for (SQLName tableName : x.getTableNames()) { + String ident = tableName.toString(); + TableStat stat = tableStats.get(ident); + if (stat == null) { + stat = new TableStat(); + tableStats.put(new TableStat.Name(ident), stat); + } + stat.incrementDeleteCount(); + } + + accept(x.getWhere()); + accept(x.getFrom()); + accept(x.getUsing()); + accept(x.getOrderBy()); + accept(x.getLimit()); + + return false; + } + + public void endVisit(MySqlDeleteStatement x) { + aliasLocal.set(null); + } + + @Override + public void endVisit(MySqlInsertStatement x) { + Mode originalMode = (Mode) x.getAttribute("_original_use_mode"); + modeLocal.set(originalMode); + } + + @Override + public boolean visit(MySqlInsertStatement x) { + x.putAttribute("_original_use_mode", modeLocal.get()); + modeLocal.set(Mode.Insert); + + aliasLocal.set(new HashMap()); + + String originalTable = currentTableLocal.get(); + + if (x.getTableName() instanceof SQLIdentifierExpr) { + String ident = ((SQLIdentifierExpr) x.getTableName()).getName(); + currentTableLocal.set(ident); + x.putAttribute("_old_local_", originalTable); + + TableStat stat = tableStats.get(ident); + if (stat == null) { + stat = new TableStat(); + tableStats.put(new TableStat.Name(ident), stat); + } + stat.incrementInsertCount(); + + Map aliasMap = aliasLocal.get(); + if (aliasMap != null) { + if (x.getAlias() != null) { + aliasMap.put(x.getAlias(), ident); + } + aliasMap.put(ident, ident); + } + } + + accept(x.getColumns()); + accept(x.getValuesList()); + accept(x.getQuery()); + accept(x.getDuplicateKeyUpdate()); + + return false; + } + + @Override + public boolean visit(MySqlBooleanExpr x) { + + return true; + } + + @Override + public void endVisit(MySqlBooleanExpr x) { + + } + + @Override + public boolean visit(Limit x) { + + return true; + } + + @Override + public void endVisit(Limit x) { + + } + + @Override + public boolean visit(MySqlTableIndex x) { + + return true; + } + + @Override + public void endVisit(MySqlTableIndex x) { + + } + + @Override + public boolean visit(MySqlKey x) { + + return true; + } + + @Override + public void endVisit(MySqlKey x) { + + } + + @Override + public boolean visit(MySqlPrimaryKey x) { + + return true; + } + + @Override + public void endVisit(MySqlPrimaryKey x) { + + } + + @Override + public void endVisit(MySqlIntervalExpr x) { + + } + + @Override + public boolean visit(MySqlIntervalExpr x) { + + return true; + } + + @Override + public void endVisit(MySqlExtractExpr x) { + + } + + @Override + public boolean visit(MySqlExtractExpr x) { + + return true; + } + + @Override + public void endVisit(MySqlMatchAgainstExpr x) { + + } + + @Override + public boolean visit(MySqlMatchAgainstExpr x) { + + return true; + } + + @Override + public void endVisit(MySqlBinaryExpr x) { + + } + + @Override + public boolean visit(MySqlBinaryExpr x) { + + return true; + } + + @Override + public void endVisit(MySqlPrepareStatement x) { + + } + + @Override + public boolean visit(MySqlPrepareStatement x) { + + return true; + } + + @Override + public void endVisit(MySqlExecuteStatement x) { + + } + + @Override + public boolean visit(MySqlExecuteStatement x) { + + return true; + } + + @Override + public void endVisit(MySqlLoadDataInFileStatement x) { + + } + + @Override + public boolean visit(MySqlLoadDataInFileStatement x) { + + return true; + } + + @Override + public void endVisit(MySqlLoadXmlStatement x) { + + } + + @Override + public boolean visit(MySqlLoadXmlStatement x) { + + return true; + } + + @Override + public void endVisit(MySqlReplicateStatement x) { + + } + + @Override + public boolean visit(MySqlReplicateStatement x) { + + return true; + } + + @Override + public void endVisit(MySqlSelectGroupBy x) { + + } + + @Override + public boolean visit(MySqlSelectGroupBy x) { + + return true; + } + + @Override + public void endVisit(MySqlStartTransactionStatement x) { + + } + + @Override + public boolean visit(MySqlStartTransactionStatement x) { + + return true; + } + + @Override + public void endVisit(MySqlCommitStatement x) { + + } + + @Override + public boolean visit(MySqlCommitStatement x) { + + return true; + } + + @Override + public void endVisit(MySqlRollbackStatement x) { + + } + + @Override + public boolean visit(MySqlRollbackStatement x) { + + return true; + } + + @Override + public void endVisit(MySqlShowColumnsStatement x) { + + } + + @Override + public boolean visit(MySqlShowColumnsStatement x) { + return true; + } + + @Override + public void endVisit(MySqlShowTablesStatement x) { + + } + + @Override + public boolean visit(MySqlShowTablesStatement x) { + return true; + } + + @Override + public void endVisit(MySqlShowDatabasesStatement x) { + + } + + @Override + public boolean visit(MySqlShowDatabasesStatement x) { + return true; + } + + @Override + public void endVisit(MySqlShowWarningsStatement x) { + + } + + @Override + public boolean visit(MySqlShowWarningsStatement x) { + return true; + } + + @Override + public void endVisit(MySqlShowStatusStatement x) { + + } + + @Override + public boolean visit(MySqlShowStatusStatement x) { + return true; + } + + @Override + public void endVisit(CobarShowStatus x) { + + } + + @Override + public boolean visit(CobarShowStatus x) { + return true; + } + + @Override + public void endVisit(MySqlKillStatement x) { + + } + + @Override + public boolean visit(MySqlKillStatement x) { + return true; + } + + @Override + public void endVisit(MySqlBinlogStatement x) { + + } + + @Override + public boolean visit(MySqlBinlogStatement x) { + return true; + } + + @Override + public void endVisit(MySqlResetStatement x) { + + } + + @Override + public boolean visit(MySqlResetStatement x) { + return true; + } } 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..7aef04e052 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/PGSelectQueryBlock.java @@ -0,0 +1,286 @@ +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; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +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 WindowClause window; + + private SQLOrderBy orderBy; + private FetchClause fetch; + private ForClause forClause; + + @Override + protected void accept0(SQLASTVisitor visitor) { + accept0((PGASTVisitor) visitor); + } + + protected void accept0(PGASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, this.with); + acceptChild(visitor, this.distinctOn); + acceptChild(visitor, this.selectList); + acceptChild(visitor, this.from); + acceptChild(visitor, this.where); + acceptChild(visitor, this.groupBy); + acceptChild(visitor, this.window); + acceptChild(visitor, this.orderBy); + acceptChild(visitor, this.limit); + acceptChild(visitor, this.offset); + acceptChild(visitor, this.fetch); + acceptChild(visitor, this.forClause); + } + visitor.endVisit(this); + } + + public FetchClause getFetch() { + return fetch; + } + + public void setFetch(FetchClause fetch) { + this.fetch = fetch; + } + + public ForClause getForClause() { + return forClause; + } + + public void setForClause(ForClause forClause) { + this.forClause = forClause; + } + + public WindowClause getWindow() { + return window; + } + + public void setWindow(WindowClause window) { + this.window = window; + } + + 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..fbe9b55cd4 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGLexer.java @@ -0,0 +1,106 @@ +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); + map.put("WITH", Token.WITH); + + 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..af27f91ca8 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGSQLStatementParser.java @@ -0,0 +1,34 @@ +package com.alibaba.druid.sql.dialect.postgresql.parser; + +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.parser.Lexer; +import com.alibaba.druid.sql.parser.ParserException; +import com.alibaba.druid.sql.parser.SQLStatementParser; +import com.alibaba.druid.sql.parser.Token; + +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); + } + + public boolean parseStatementListDialect(List statementList) { + if (lexer.token() == Token.WITH) { + SQLStatement stmt = parseSelect(); + statementList.add(stmt); + return true; + } + + return false; + } +} 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..486ebef49d --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/parser/PGSelectParser.java @@ -0,0 +1,229 @@ +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(); + } + + parseSelectList(queryBlock); + + 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.setWindow(window); + } + + 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'"); + } + + queryBlock.setFetch(fetch); + } + + 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); + } + + queryBlock.setForClause(forClause); + } + + return queryRest(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) { + lexer.nextToken(); + 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/dialect/postgresql/visitor/PGASTVisitorAdapter.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGASTVisitorAdapter.java new file mode 100644 index 0000000000..a28567f054 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGASTVisitorAdapter.java @@ -0,0 +1,68 @@ +package com.alibaba.druid.sql.dialect.postgresql.visitor; + +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.FetchClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.ForClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.WindowClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.WithClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.WithQuery; +import com.alibaba.druid.sql.visitor.SQLASTVisitorAdapter; + +public class PGASTVisitorAdapter extends SQLASTVisitorAdapter implements + PGASTVisitor { + + @Override + public void endVisit(WindowClause x) { + + } + + @Override + public boolean visit(WindowClause x) { + + return true; + } + + @Override + public void endVisit(FetchClause x) { + + } + + @Override + public boolean visit(FetchClause x) { + + return true; + } + + @Override + public void endVisit(ForClause x) { + + } + + @Override + public boolean visit(ForClause x) { + + return true; + } + + @Override + public void endVisit(WithQuery x) { + + } + + @Override + public boolean visit(WithQuery x) { + + return true; + } + + @Override + public void endVisit(WithClause x) { + + } + + @Override + public boolean visit(WithClause x) { + + return true; + } + +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGOutputVisitor.java new file mode 100644 index 0000000000..f000609ecb --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGOutputVisitor.java @@ -0,0 +1,213 @@ +package com.alibaba.druid.sql.dialect.postgresql.visitor; + +import com.alibaba.druid.sql.ast.SQLSetQuantifier; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.FetchClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.ForClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.WindowClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.WithClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.WithQuery; +import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; + +public class PGOutputVisitor extends SQLASTOutputVisitor implements + PGASTVisitor { + + public PGOutputVisitor(Appendable appender) { + super(appender); + } + + @Override + public void endVisit(WindowClause x) { + + } + + @Override + public boolean visit(WindowClause x) { + print("WINDOW "); + x.getName().accept(this); + print(" AS "); + for (int i = 0; i < x.getDefinition().size(); ++i) { + if (i != 0) { + println(", "); + } + print("("); + x.getDefinition().get(i).accept(this); + print(")"); + } + return false; + } + + @Override + public void endVisit(FetchClause x) { + + } + + @Override + public boolean visit(FetchClause x) { + print("FETCH "); + if (FetchClause.Option.FIRST.equals(x.getOption())) { + print("FIRST "); + } else if (FetchClause.Option.NEXT.equals(x.getOption())) { + print("NEXT "); + } + x.getCount().accept(this); + print(" ROWS ONLY"); + return false; + } + + @Override + public void endVisit(ForClause x) { + + } + + @Override + public boolean visit(ForClause x) { + print("FOR "); + if (ForClause.Option.UPDATE.equals(x.getOption())) { + print("UPDATE "); + } else if (ForClause.Option.SHARE.equals(x.getOption())) { + print("SHARE "); + } + + if (x.getOf().size() > 0) { + for (int i = 0; i < x.getOf().size(); ++i) { + if (i != 0) { + println(", "); + } + x.getOf().get(i).accept(this); + } + } + + if (x.isNoWait()) { + print(" NOWAIT"); + } + + return false; + } + + @Override + public void endVisit(WithQuery x) { + + } + + @Override + public boolean visit(WithQuery x) { + x.getName().accept(this); + + if (x.getColumns().size() > 0) { + print(" ("); + printAndAccept(x.getColumns(), ", "); + print(")"); + } + println(); + print("AS"); + println(); + print("("); + incrementIndent(); + println(); + x.getSubQuery().accept(this); + decrementIndent(); + println(); + print(")"); + + return false; + } + + @Override + public void endVisit(WithClause x) { + + } + + @Override + public boolean visit(WithClause x) { + print("WITH"); + incrementIndent(); + println(); + printlnAndAccept(x.getWithQuery(), ", "); + decrementIndent(); + return false; + } + + public boolean visit(SQLSelectQueryBlock x) { + if (x instanceof PGSelectQueryBlock) { + return visit((PGSelectQueryBlock) x); + } + + return super.visit(x); + } + + public boolean visit(PGSelectQueryBlock x) { + if (x.getWith() != null) { + x.getWith().accept(this); + println(); + } + + print("SELECT "); + + if (SQLSetQuantifier.ALL == x.getDistionOption()) { + print("ALL "); + } else if (SQLSetQuantifier.DISTINCT == x.getDistionOption()) { + print("DISTINCT "); + + if (x.getDistinctOn() != null) { + print("ON "); + printAndAccept(x.getDistinctOn(), ", "); + } + } + + printSelectList(x.getSelectList()); + + if (x.getFrom() != null) { + println(); + print("FROM "); + x.getFrom().accept(this); + } + + if (x.getWhere() != null) { + println(); + print("WHERE "); + x.getWhere().accept(this); + } + + if (x.getGroupBy() != null) { + print(" "); + x.getGroupBy().accept(this); + } + + if (x.getWindow() != null) { + println(); + x.getWindow().accept(this); + } + + if (x.getOrderBy() != null) { + println(); + x.getOrderBy().accept(this); + } + + if (x.getLimit() != null) { + println(); + print("LIMIT "); + x.getLimit().accept(this); + } + + if (x.getOffset() != null) { + println(); + print("OFFSET "); + x.getOffset().accept(this); + print(" ROWS"); + } + + if (x.getFetch() != null) { + println(); + x.getFetch().accept(this); + } + + if (x.getForClause() != null) { + println(); + x.getForClause().accept(this); + } + + return false; + } +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGSchemaStatVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGSchemaStatVisitor.java new file mode 100644 index 0000000000..83c6cac432 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/visitor/PGSchemaStatVisitor.java @@ -0,0 +1,66 @@ +package com.alibaba.druid.sql.dialect.postgresql.visitor; + +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.FetchClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.ForClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.WindowClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.WithClause; +import com.alibaba.druid.sql.dialect.postgresql.ast.PGSelectQueryBlock.WithQuery; +import com.alibaba.druid.sql.visitor.SchemaStatVisitor; + +public class PGSchemaStatVisitor extends SchemaStatVisitor implements + PGASTVisitor { + + @Override + public void endVisit(WindowClause x) { + + } + + @Override + public boolean visit(WindowClause x) { + return true; + } + + @Override + public void endVisit(FetchClause x) { + + } + + @Override + public boolean visit(FetchClause x) { + return true; + } + + @Override + public void endVisit(ForClause x) { + + } + + @Override + public boolean visit(ForClause x) { + + return true; + } + + @Override + public void endVisit(WithQuery x) { + + } + + @Override + public boolean visit(WithQuery x) { + + return true; + } + + @Override + public void endVisit(WithClause x) { + + } + + @Override + public boolean visit(WithClause x) { + + return true; + } + +} 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"), diff --git a/src/main/java/com/alibaba/druid/sql/visitor/SchemaStatVisitor.java b/src/main/java/com/alibaba/druid/sql/visitor/SchemaStatVisitor.java new file mode 100644 index 0000000000..aa489c9668 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/visitor/SchemaStatVisitor.java @@ -0,0 +1,283 @@ +package com.alibaba.druid.sql.visitor; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.stat.TableStat; +import com.alibaba.druid.stat.TableStat.Column; +import com.alibaba.druid.stat.TableStat.Mode; + +public class SchemaStatVisitor extends SQLASTVisitorAdapter { + protected HashMap tableStats = new HashMap(); + protected Set fields = new HashSet(); + + protected final static ThreadLocal> aliasLocal = new ThreadLocal>(); + protected final static ThreadLocal currentTableLocal = new ThreadLocal(); + protected final static ThreadLocal modeLocal = new ThreadLocal(); + + @Override + public boolean visit(SQLInsertStatement x) { + x.putAttribute("_original_use_mode", modeLocal.get()); + modeLocal.set(Mode.Insert); + + aliasLocal.set(new HashMap()); + + String originalTable = currentTableLocal.get(); + + if (x.getTableName() instanceof SQLIdentifierExpr) { + String ident = ((SQLIdentifierExpr) x.getTableName()).getName(); + currentTableLocal.set(ident); + x.putAttribute("_old_local_", originalTable); + + TableStat stat = tableStats.get(ident); + if (stat == null) { + stat = new TableStat(); + tableStats.put(new TableStat.Name(ident), stat); + } + stat.incrementInsertCount(); + + Map aliasMap = aliasLocal.get(); + if (aliasMap != null) { + if (x.getAlias() != null) { + aliasMap.put(x.getAlias(), ident); + } + aliasMap.put(ident, ident); + } + } + + accept(x.getColumns()); + accept(x.getQuery()); + + return false; + } + + protected void accept(SQLObject x) { + if (x != null) { + x.accept(this); + } + } + + protected void accept(List nodes) { + for (int i = 0, size = nodes.size(); i < size; ++i) { + accept(nodes.get(i)); + } + } + + public boolean visit(SQLSelectQueryBlock x) { + + if (x.getFrom() instanceof SQLSubqueryTableSource) { + x.getFrom().accept(this); + return false; + } + + x.putAttribute("_original_use_mode", modeLocal.get()); + modeLocal.set(Mode.Select); + + String originalTable = currentTableLocal.get(); + + if (x.getFrom() instanceof SQLExprTableSource) { + SQLExprTableSource tableSource = (SQLExprTableSource) x.getFrom(); + if (tableSource.getExpr() instanceof SQLIdentifierExpr) { + String ident = ((SQLIdentifierExpr) tableSource.getExpr()) + .getName(); + currentTableLocal.set(ident); + x.putAttribute("_old_local_", originalTable); + } + } + + x.getFrom().accept(this); // 提前执行,获得aliasMap + + return true; + } + + public void endVisit(SQLSelectQueryBlock x) { + String originalTable = (String) x.getAttribute("_old_local_"); + x.putAttribute("table", currentTableLocal.get()); + currentTableLocal.set(originalTable); + + Mode originalMode = (Mode) x.getAttribute("_original_use_mode"); + modeLocal.set(originalMode); + } + + public boolean visit(SQLJoinTableSource x) { + return true; + } + + public boolean visit(SQLPropertyExpr x) { + if (x.getOwner() instanceof SQLIdentifierExpr) { + String owner = ((SQLIdentifierExpr) x.getOwner()).getName(); + + if (owner != null) { + Map aliasMap = aliasLocal.get(); + if (aliasMap != null) { + String table = aliasMap.get(owner); + + // table == null时是SubQuery + if (table != null) { + fields.add(new Column(table, x.getName())); + } + } + } + } + return false; + } + + public boolean visit(SQLIdentifierExpr x) { + String currentTable = currentTableLocal.get(); + + if (currentTable != null) { + fields.add(new Column(currentTable, x.getName())); + } + return false; + } + + public boolean visit(SQLAllColumnExpr x) { + String currentTable = currentTableLocal.get(); + + if (currentTable != null) { + fields.add(new Column(currentTable, "*")); + } + return false; + } + + public Map getTables() { + return tableStats; + } + + public boolean containsTable(String tableName) { + return tableStats.containsKey(new TableStat.Name(tableName)); + } + + public Set getFields() { + return fields; + } + + public boolean visit(SQLSelectStatement x) { + aliasLocal.set(new HashMap()); + return true; + } + + public void endVisit(SQLSelectStatement x) { + aliasLocal.set(null); + } + + public boolean visit(SQLSubqueryTableSource x) { + Map aliasMap = aliasLocal.get(); + if (aliasMap != null) { + if (x.getAlias() != null) { + aliasMap.put(x.getAlias(), null); + } + } + return true; + } + + public boolean visit(SQLExprTableSource x) { + if (x.getExpr() instanceof SQLIdentifierExpr + || x.getExpr() instanceof SQLPropertyExpr) { + String ident = x.getExpr().toString(); + TableStat stat = tableStats.get(ident); + if (stat == null) { + stat = new TableStat(); + tableStats.put(new TableStat.Name(ident), stat); + } + + Mode mode = modeLocal.get(); + switch (mode) { + case Delete: + stat.incrementDeleteCount(); + break; + case Insert: + stat.incrementInsertCount(); + break; + case Update: + stat.incrementUpdateCount(); + break; + case Select: + stat.incrementSelectCount(); + break; + default: + break; + } + + Map aliasMap = aliasLocal.get(); + if (aliasMap != null) { + if (x.getAlias() != null) { + aliasMap.put(x.getAlias(), ident); + } + aliasMap.put(ident, ident); + } + } else { + accept(x.getExpr()); + } + + return false; + } + + public boolean visit(SQLSelect x) { + accept(x.getQuery()); + + String originalTable = currentTableLocal.get(); + + currentTableLocal.set((String) x.getQuery().getAttribute("table")); + x.putAttribute("_old_local_", originalTable); + + accept(x.getOrderBy()); + + currentTableLocal.set(originalTable); + + return false; + } + + public boolean visit(SQLAggregateExpr x) { + accept(x.getArguments()); + return false; + } + + public boolean visit(SQLMethodInvokeExpr x) { + accept(x.getParameters()); + return false; + } + + public boolean visit(SQLUpdateStatement x) { + aliasLocal.set(new HashMap()); + + String ident = x.getTableName().toString(); + currentTableLocal.set(ident); + + TableStat stat = tableStats.get(ident); + if (stat == null) { + stat = new TableStat(); + tableStats.put(new TableStat.Name(ident), stat); + } + stat.incrementUpdateCount(); + + Map aliasMap = aliasLocal.get(); + aliasMap.put(ident, ident); + + accept(x.getItems()); + accept(x.getWhere()); + + return false; + } + + public void endVisit(SQLUpdateStatement x) { + aliasLocal.set(null); + } +} diff --git a/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PostgresqlResourceTest.java b/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PostgresqlResourceTest.java new file mode 100644 index 0000000000..44da320ae5 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PostgresqlResourceTest.java @@ -0,0 +1,83 @@ +package com.alibaba.druid.bvt.sql.postgresql; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.List; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser; +import com.alibaba.druid.sql.dialect.postgresql.visitor.PGOutputVisitor; +import com.alibaba.druid.sql.dialect.postgresql.visitor.PGSchemaStatVisitor; +import com.alibaba.druid.util.JdbcUtils; + +public class PostgresqlResourceTest extends TestCase { + + public void test_0() throws Exception { + // 13 + exec_test("bvt/parser/postgresql-0.txt"); + // for (int i = 0; i <= 53; ++i) { + // exec_test("bvt/parser/oracle-" + i + ".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 = JdbcUtils.read(reader); + JdbcUtils.close(reader); + String[] items = input.split("---------------------------"); + String sql = items[0].trim(); + String expect = items[1].trim(); + + PGSQLStatementParser parser = new PGSQLStatementParser(sql); + List statementList = parser.parseStatementList(); + SQLStatement statemen = statementList.get(0); + System.out.println(output(statementList)); + + Assert.assertEquals(1, statementList.size()); + + PGSchemaStatVisitor visitor = new PGSchemaStatVisitor(); + statemen.accept(visitor); + + System.out.println(sql); + System.out.println("Tables : " + visitor.getTables()); + System.out.println("fields : " + visitor.getFields()); + + System.out.println(); + System.out.println(); + } + + void mergValidate(String sql, String expect) { + PGSQLStatementParser parser = new PGSQLStatementParser(sql); + List statementList = parser.parseStatementList(); + SQLStatement statemen = statementList.get(0); + + Assert.assertEquals(1, statementList.size()); + + StringBuilder out = new StringBuilder(); + PGOutputVisitor visitor = new PGOutputVisitor(out); + statemen.accept(visitor); + + System.out.println(out.toString()); + + Assert.assertEquals(expect, out.toString()); + } + + private String output(List stmtList) { + StringBuilder out = new StringBuilder(); + PGOutputVisitor visitor = new PGOutputVisitor(out); + + for (SQLStatement stmt : stmtList) { + stmt.accept(visitor); + } + + return out.toString(); + } +} diff --git a/src/test/resources/bvt/parser/postgresql-0.txt b/src/test/resources/bvt/parser/postgresql-0.txt new file mode 100644 index 0000000000..1092cd4161 --- /dev/null +++ b/src/test/resources/bvt/parser/postgresql-0.txt @@ -0,0 +1,19 @@ +WITH RECURSIVE employee_recursive(distance, employee_name, manager_name) AS ( + SELECT 1, employee_name, manager_name + FROM employee + WHERE manager_name = 'Mary' + UNION ALL + SELECT er.distance + 1, e.employee_name, e.manager_name + FROM employee_recursive er, employee e + WHERE er.employee_name = e.manager_name + ) +SELECT distance, employee_name FROM employee_recursive; +--------------------------- +SELECT e1.last_name +FROM employees e1 +WHERE f(CURSOR(( + SELECT e2.hire_date + FROM employees e2 + WHERE e1.employee_id = e2.manager_id + )), e1.hire_date) = 1 +ORDER BY last_name;