From 9256b904db2d0ceb6053323f3868cd08ef4cd1f2 Mon Sep 17 00:00:00 2001 From: wenshao Date: Mon, 23 Oct 2017 19:38:40 +0800 Subject: [PATCH] improved postgresql parser. bug fixed for issue #2060 --- .../druid/sql/ast/expr/SQLCaseExpr.java | 5 ++ .../ast/stmt/PGInsertStatement.java | 65 ++++++++++++++++++- .../parser/PGSQLStatementParser.java | 51 +++++++++++++++ .../postgresql/visitor/PGOutputVisitor.java | 36 ++++++++++ .../java/com/alibaba/druid/util/FnvHash.java | 2 + .../bvt/sql/postgresql/PGInsertTest7.java | 57 ++++++++++++++++ .../bvt/sql/postgresql/PGInsertTest8.java | 58 +++++++++++++++++ .../bvt/sql/postgresql/PGInsertTest9.java | 58 +++++++++++++++++ 8 files changed, 329 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest7.java create mode 100644 src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest8.java create mode 100644 src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest9.java diff --git a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLCaseExpr.java b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLCaseExpr.java index df0a4e7461..dc94f2e7aa 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/expr/SQLCaseExpr.java +++ b/src/main/java/com/alibaba/druid/sql/ast/expr/SQLCaseExpr.java @@ -22,6 +22,7 @@ import java.util.List; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.*; +import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; import com.alibaba.druid.sql.visitor.SQLASTVisitor; public class SQLCaseExpr extends SQLExprImpl implements SQLReplaceable, Serializable { @@ -187,6 +188,10 @@ public class SQLCaseExpr extends SQLExprImpl implements SQLReplaceable, Serializ return x; } + public void output(StringBuffer buf) { + new SQLASTOutputVisitor(buf).visit(this); + } + @Override public boolean replace(SQLExpr expr, SQLExpr target) { if (valueExpr == expr) { diff --git a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/stmt/PGInsertStatement.java b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/stmt/PGInsertStatement.java index 7d0eec5eff..5078a9fa28 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/stmt/PGInsertStatement.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/postgresql/ast/stmt/PGInsertStatement.java @@ -19,7 +19,9 @@ import java.util.ArrayList; import java.util.List; import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLName; import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; import com.alibaba.druid.sql.ast.statement.SQLWithSubqueryClause; import com.alibaba.druid.sql.dialect.postgresql.visitor.PGASTVisitor; import com.alibaba.druid.sql.visitor.SQLASTVisitor; @@ -28,9 +30,15 @@ import com.alibaba.druid.util.JdbcConstants; public class PGInsertStatement extends SQLInsertStatement implements PGSQLStatement { - private List valuesList = new ArrayList(); - private SQLExpr returning; - private boolean defaultValues = false; + private List valuesList = new ArrayList(); + private SQLExpr returning; + private boolean defaultValues = false; + + private List onConflictTarget; + private SQLName onConflictConstraint; + private SQLExpr onConflictWhere; + private boolean onConflictDoNothing; + private List onConflictUpdateSetItems; public PGInsertStatement() { dbType = JdbcConstants.POSTGRESQL; @@ -113,4 +121,55 @@ public class PGInsertStatement extends SQLInsertStatement implements PGSQLStatem cloneTo(x); return x; } + + public List getOnConflictTarget() { + return onConflictTarget; + } + + public void setOnConflictTarget(List onConflictTarget) { + this.onConflictTarget = onConflictTarget; + } + + public boolean isOnConflictDoNothing() { + return onConflictDoNothing; + } + + public void setOnConflictDoNothing(boolean onConflictDoNothing) { + this.onConflictDoNothing = onConflictDoNothing; + } + + public List getOnConflictUpdateSetItems() { + return onConflictUpdateSetItems; + } + + public void addConflicUpdateItem(SQLUpdateSetItem item) { + if (onConflictUpdateSetItems == null) { + onConflictUpdateSetItems = new ArrayList(); + } + + item.setParent(this); + onConflictUpdateSetItems.add(item); + } + + public SQLName getOnConflictConstraint() { + return onConflictConstraint; + } + + public void setOnConflictConstraint(SQLName x) { + if (x != null) { + x.setParent(this); + } + this.onConflictConstraint = x; + } + + public SQLExpr getOnConflictWhere() { + return onConflictWhere; + } + + public void setOnConflictWhere(SQLExpr x) { + if (x != null) { + x.setParent(this); + } + this.onConflictWhere = x; + } } 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 index c171146402..fa0dd73636 100644 --- 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 @@ -150,6 +150,57 @@ public class PGSQLStatementParser extends SQLStatementParser { stmt.setQuery(queryExpr.getSubQuery()); } + if (lexer.token() == Token.ON) { + lexer.nextToken(); + if (lexer.identifierEquals(FnvHash.Constants.CONFLICT)) { + lexer.nextToken(); + + if (lexer.token() == Token.LPAREN) { + lexer.nextToken(); + List onConflictTarget = new ArrayList(); + this.exprParser.exprList(onConflictTarget, stmt); + stmt.setOnConflictTarget(onConflictTarget); + accept(Token.RPAREN); + } + + if (lexer.token() == Token.ON) { + lexer.nextToken(); + accept(Token.CONSTRAINT); + SQLName constraintName = this.exprParser.name(); + stmt.setOnConflictConstraint(constraintName); + } + + if (lexer.token() == Token.WHERE) { + lexer.nextToken(); + SQLExpr where = this.exprParser.expr(); + stmt.setOnConflictWhere(where); + } + + if (lexer.token() == Token.DO) { + lexer.nextToken(); + + if (lexer.identifierEquals(FnvHash.Constants.NOTHING)) { + lexer.nextToken(); + stmt.setOnConflictDoNothing(true); + } else { + accept(Token.UPDATE); + accept(Token.SET); + + for (;;) { + SQLUpdateSetItem item = this.exprParser.parseUpdateSetItem(); + stmt.addConflicUpdateItem(item); + + if (lexer.token() != Token.COMMA) { + break; + } + + lexer.nextToken(); + } + } + } + } + } + if (lexer.token() == Token.RETURNING) { lexer.nextToken(); SQLExpr returning = this.exprParser.expr(); 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 index df7956402f..0705b8384a 100644 --- 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 @@ -305,6 +305,42 @@ public class PGOutputVisitor extends SQLASTOutputVisitor implements PGASTVisitor } } + List onConflictTarget = x.getOnConflictTarget(); + List onConflictUpdateSetItems = x.getOnConflictUpdateSetItems(); + boolean onConflictDoNothing = x.isOnConflictDoNothing(); + + if (onConflictDoNothing + || (onConflictTarget != null && onConflictTarget.size() > 0) + || (onConflictUpdateSetItems != null && onConflictUpdateSetItems.size() > 0)) { + println(); + print0(ucase ? "ON CONFLICT" : "on conflict"); + + if ((onConflictTarget != null && onConflictTarget.size() > 0)) { + print0(" ("); + printAndAccept(onConflictTarget, ", "); + print(')'); + } + + SQLName onConflictConstraint = x.getOnConflictConstraint(); + if (onConflictConstraint != null) { + print0(ucase ? " ON CONSTRAINT " : " on constraint "); + printExpr(onConflictConstraint); + } + + SQLExpr onConflictWhere = x.getOnConflictWhere(); + if (onConflictWhere != null) { + print0(ucase ? " WHERE " : " where "); + printExpr(onConflictWhere); + } + + if (onConflictDoNothing) { + print0(ucase ? " DO NOTHING" : " do nothing"); + } else if ((onConflictUpdateSetItems != null && onConflictUpdateSetItems.size() > 0)) { + print0(ucase ? " UPDATE SET " : " update set "); + printAndAccept(onConflictUpdateSetItems, ", "); + } + } + if (x.getReturning() != null) { println(); print0(ucase ? "RETURNING " : "returning "); diff --git a/src/main/java/com/alibaba/druid/util/FnvHash.java b/src/main/java/com/alibaba/druid/util/FnvHash.java index f0583f0c10..01c7a5a298 100644 --- a/src/main/java/com/alibaba/druid/util/FnvHash.java +++ b/src/main/java/com/alibaba/druid/util/FnvHash.java @@ -568,6 +568,8 @@ public final class FnvHash { long ENDS = fnv1a_64_lower("ENDS"); long BINARY = fnv1a_64_lower("BINARY"); long ISOPEN = fnv1a_64_lower("ISOPEN"); + long CONFLICT = fnv1a_64_lower("CONFLICT"); + long NOTHING = fnv1a_64_lower("NOTHING"); long RS = fnv1a_64_lower("RS"); long RR = fnv1a_64_lower("RR"); diff --git a/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest7.java b/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest7.java new file mode 100644 index 0000000000..673739f425 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest7.java @@ -0,0 +1,57 @@ +/* + * 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.bvt.sql.postgresql; + +import com.alibaba.druid.sql.PGTest; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser; +import com.alibaba.druid.sql.dialect.postgresql.visitor.PGSchemaStatVisitor; +import com.alibaba.druid.stat.TableStat; +import org.junit.Assert; + +import java.util.List; + +public class PGInsertTest7 extends PGTest { + + public void test_0() throws Exception { + String sql = "insert into test(k,v) values (1,2) ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v; "; + + PGSQLStatementParser parser = new PGSQLStatementParser(sql); + List statementList = parser.parseStatementList(); + SQLStatement stmt = statementList.get(0); +// print(statementList); + + Assert.assertEquals(1, statementList.size()); + + PGSchemaStatVisitor visitor = new PGSchemaStatVisitor(); + stmt.accept(visitor); + + System.out.println("Tables : " + visitor.getTables()); + System.out.println("fields : " + visitor.getColumns()); +// System.out.println("coditions : " + visitor.getConditions()); + + Assert.assertTrue(visitor.getTables().containsKey(new TableStat.Name("test"))); + Assert.assertEquals(2, visitor.getColumns().size()); + + Assert.assertTrue(visitor.getColumns().contains(new TableStat.Column("test", "k"))); + Assert.assertTrue(visitor.getColumns().contains(new TableStat.Column("test", "v"))); + + assertEquals("INSERT INTO test (k, v)\n" + + "VALUES (1, 2)\n" + + "ON CONFLICT (k) UPDATE SET v = EXCLUDED.v;", stmt.toString()); + } + +} diff --git a/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest8.java b/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest8.java new file mode 100644 index 0000000000..8ef3ced36b --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest8.java @@ -0,0 +1,58 @@ +/* + * 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.bvt.sql.postgresql; + +import com.alibaba.druid.sql.PGTest; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser; +import com.alibaba.druid.sql.dialect.postgresql.visitor.PGSchemaStatVisitor; +import com.alibaba.druid.stat.TableStat; +import org.junit.Assert; + +import java.util.List; + +public class PGInsertTest8 extends PGTest { + + public void test_0() throws Exception { + String sql = "INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')\n" + + " ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING;"; + + PGSQLStatementParser parser = new PGSQLStatementParser(sql); + List statementList = parser.parseStatementList(); + SQLStatement stmt = statementList.get(0); +// print(statementList); + + Assert.assertEquals(1, statementList.size()); + + PGSchemaStatVisitor visitor = new PGSchemaStatVisitor(); + stmt.accept(visitor); + + System.out.println("Tables : " + visitor.getTables()); + System.out.println("fields : " + visitor.getColumns()); +// System.out.println("coditions : " + visitor.getConditions()); + + Assert.assertTrue(visitor.getTables().containsKey(new TableStat.Name("distributors"))); + Assert.assertEquals(2, visitor.getColumns().size()); + + Assert.assertTrue(visitor.getColumns().contains(new TableStat.Column("distributors", "did"))); + Assert.assertTrue(visitor.getColumns().contains(new TableStat.Column("distributors", "dname"))); + + assertEquals("INSERT INTO distributors (did, dname)\n" + + "VALUES (9, 'Antwerp Design')\n" + + "ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING;", stmt.toString()); + } + +} diff --git a/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest9.java b/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest9.java new file mode 100644 index 0000000000..9c8f738ff6 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/postgresql/PGInsertTest9.java @@ -0,0 +1,58 @@ +/* + * 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.bvt.sql.postgresql; + +import com.alibaba.druid.sql.PGTest; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser; +import com.alibaba.druid.sql.dialect.postgresql.visitor.PGSchemaStatVisitor; +import com.alibaba.druid.stat.TableStat; +import org.junit.Assert; + +import java.util.List; + +public class PGInsertTest9 extends PGTest { + + public void test_0() throws Exception { + String sql = "INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')\n" + + " ON CONFLICT (did) WHERE is_active DO NOTHING;"; + + PGSQLStatementParser parser = new PGSQLStatementParser(sql); + List statementList = parser.parseStatementList(); + SQLStatement stmt = statementList.get(0); +// print(statementList); + + Assert.assertEquals(1, statementList.size()); + + PGSchemaStatVisitor visitor = new PGSchemaStatVisitor(); + stmt.accept(visitor); + + System.out.println("Tables : " + visitor.getTables()); + System.out.println("fields : " + visitor.getColumns()); +// System.out.println("coditions : " + visitor.getConditions()); + + Assert.assertTrue(visitor.getTables().containsKey(new TableStat.Name("distributors"))); + Assert.assertEquals(2, visitor.getColumns().size()); + + Assert.assertTrue(visitor.getColumns().contains(new TableStat.Column("distributors", "did"))); + Assert.assertTrue(visitor.getColumns().contains(new TableStat.Column("distributors", "dname"))); + + assertEquals("INSERT INTO distributors (did, dname)\n" + + "VALUES (10, 'Conrad International')\n" + + "ON CONFLICT (did) WHERE is_active DO NOTHING;", stmt.toString()); + } + +}