From 7d5f0cf8da4f1bcedf6358d1144173fb75702f8c Mon Sep 17 00:00:00 2001 From: Yako Date: Mon, 4 Aug 2014 13:37:21 +0800 Subject: [PATCH] bugfix for 'FOREIGN KEY reference' --- .../dialect/mysql/ast/MysqlForeignKey.java | 54 +++++++++++++ .../dialect/mysql/parser/MySqlExprParser.java | 50 +++++++++++- .../mysql/visitor/MySqlOutputVisitor.java | 14 ++++ .../bvt/sql/mysql/MySqlCreateTableTest54.java | 81 ++++++++++++++++++- 4 files changed, 195 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/alibaba/druid/sql/dialect/mysql/ast/MysqlForeignKey.java b/src/main/java/com/alibaba/druid/sql/dialect/mysql/ast/MysqlForeignKey.java index 3335e959f8..29437bde4d 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/mysql/ast/MysqlForeignKey.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/mysql/ast/MysqlForeignKey.java @@ -29,6 +29,12 @@ public class MysqlForeignKey extends SQLForeignKeyImpl { private boolean hasConstaint; + private Match referenceMatch; + + private On referenceOn; + + private Option referenceOption; + public SQLName getIndexName() { return indexName; } @@ -63,4 +69,52 @@ public class MysqlForeignKey extends SQLForeignKeyImpl { } visitor.endVisit(this); } + + public Match getReferenceMatch() { + return referenceMatch; + } + + public void setReferenceMatch(Match referenceMatch) { + this.referenceMatch = referenceMatch; + } + + public On getReferenceOn() { + return referenceOn; + } + + public void setReferenceOn(On referenceOn) { + this.referenceOn = referenceOn; + } + + public Option getReferenceOption() { + return referenceOption; + } + + public void setReferenceOption(Option referenceOption) { + this.referenceOption = referenceOption; + } + + public static enum Option { + + RESTRICT("RESTRICT"), CASCADE("CASCADE"), SET_NULL("SET NULL"), NO_ACTION("NO ACTION"); + + private String text; + + Option(String text){ + this.text = text; + } + + public String getText() { + return text; + } + + } + + public static enum Match { + FULL, PARTIAL, SIMPLE; + } + + public static enum On { + DELETE, UPDATE; + } } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlExprParser.java b/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlExprParser.java index ecfea5f794..5641b047b4 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlExprParser.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlExprParser.java @@ -35,6 +35,9 @@ import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition; import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey; import com.alibaba.druid.sql.dialect.mysql.ast.MySqlUnique; import com.alibaba.druid.sql.dialect.mysql.ast.MysqlForeignKey; +import com.alibaba.druid.sql.dialect.mysql.ast.MysqlForeignKey.Match; +import com.alibaba.druid.sql.dialect.mysql.ast.MysqlForeignKey.On; +import com.alibaba.druid.sql.dialect.mysql.ast.MysqlForeignKey.Option; import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlBinaryExpr; import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlCharExpr; import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlExtractExpr; @@ -249,7 +252,7 @@ public class MySqlExprParser extends SQLExprParser { if (lexer.token() == Token.EQ) { lexer.nextToken(); } - + if (lexer.token() != Token.IDENTIFIER) { throw new ParserException("syntax error"); } @@ -484,7 +487,7 @@ public class MySqlExprParser extends SQLExprParser { return userName; } - if(lexer.token() == Token.ERROR) { + if (lexer.token() == Token.ERROR) { throw new ParserException("syntax error, token: " + lexer.token() + " " + lexer.stringVal() + ", pos : " + lexer.pos()); } @@ -580,7 +583,7 @@ public class MySqlExprParser extends SQLExprParser { lexer.nextToken(); dataType.getAttributes().put("UNSIGNED", true); } - + if (identifierEquals("ZEROFILL")) { lexer.nextToken(); dataType.getAttributes().put("ZEROFILL", true); @@ -782,6 +785,47 @@ public class MySqlExprParser extends SQLExprParser { accept(Token.LPAREN); this.names(fk.getReferencedColumns()); accept(Token.RPAREN); + + if (identifierEquals("MATCH")) { + if (identifierEquals("FULL")) { + fk.setReferenceMatch(Match.FULL); + } else if (identifierEquals("PARTIAL")) { + fk.setReferenceMatch(Match.PARTIAL); + } else if (identifierEquals("SIMPLE")) { + fk.setReferenceMatch(Match.SIMPLE); + } + } + + if (lexer.token() == Token.ON) { + lexer.nextToken(); + if (lexer.token() == Token.DELETE) { + fk.setReferenceOn(On.DELETE); + } else if (lexer.token() == Token.UPDATE) { + fk.setReferenceOn(On.UPDATE); + } else { + throw new ParserException("syntax error, expect DELETE or UPDATE, actual " + lexer.token() + " " + + lexer.stringVal()); + } + lexer.nextToken(); + + if (lexer.token() == Token.RESTRICT) { + fk.setReferenceOption(Option.RESTRICT); + } else if (identifierEquals("CASCADE")) { + fk.setReferenceOption(Option.CASCADE); + } else if (lexer.token() == Token.SET) { + accept(Token.NULL); + fk.setReferenceOption(Option.SET_NULL); + } else if (identifierEquals("ON")) { + lexer.nextToken(); + if (identifierEquals("ACTION")) { + fk.setReferenceOption(Option.NO_ACTION); + } else { + throw new ParserException("syntax error, expect ACTION, actual " + lexer.token() + " " + + lexer.stringVal()); + } + } + lexer.nextToken(); + } return fk; } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlOutputVisitor.java index 79787870aa..a26fb22fc9 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlOutputVisitor.java @@ -2862,6 +2862,20 @@ public class MySqlOutputVisitor extends SQLASTOutputVisitor implements MySqlASTV print(" ("); printAndAccept(x.getReferencedColumns(), ", "); print(")"); + + if(x.getReferenceMatch() != null) { + print(" MATCH "); + print(x.getReferenceMatch().name()); + } + + if(x.getReferenceOn()!= null) { + print(" ON "); + print(x.getReferenceOn().name()); + print(" "); + if(x.getReferenceOption() != null) { + print(x.getReferenceOption().getText()); + } + } return false; } diff --git a/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlCreateTableTest54.java b/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlCreateTableTest54.java index 25a013b167..6fe40e3c00 100644 --- a/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlCreateTableTest54.java +++ b/src/test/java/com/alibaba/druid/bvt/sql/mysql/MySqlCreateTableTest54.java @@ -28,7 +28,7 @@ public class MySqlCreateTableTest54 extends MysqlTest { @Test public void test_UNSIGNED_ZEROFILL() throws Exception { - String sql = "CREATE TABLE t1 (year YEAR(4), month INT(2) UNSIGNED ZEROFILL, day INT(2) UNSIGNED ZEROFILL);';"; + String sql = "CREATE TABLE t1 (year YEAR(4), month INT(2) UNSIGNED ZEROFILL, day INT(2) UNSIGNED ZEROFILL);"; MySqlStatementParser parser = new MySqlStatementParser(sql); SQLStatement stmt = parser.parseCreateTable(); @@ -50,4 +50,83 @@ public class MySqlCreateTableTest54 extends MysqlTest { } + @Test + public void test_FOREIGN_KEY() throws Exception { + String sql = "CREATE TABLE `t_activity_node` (" + + "\n`id` bigint(20) NOT NULL," + + "\n`sellerId` bigint(20) DEFAULT NULL," + + "\n`canvas_id` bigint(20) NOT NULL COMMENT '画布ID'," + + "\n`view_node_id` bigint(20) NOT NULL COMMENT '对应显示的节点id'," + + "\n`activity_type` int(11) NOT NULL COMMENT '活动类型'," + + "\n`node_type` int(11) NOT NULL COMMENT '节点类型'," + + "\n`node_title` varchar(200) NOT NULL COMMENT '节点标题'," + + "\n`status` int(11) NOT NULL DEFAULT '0' COMMENT '页面的显示状态'," + + "\n`update_status` int(11) DEFAULT NULL COMMENT '节点创建后的修改状态'," + + "\n`execute_status` int(11) DEFAULT NULL COMMENT '节点当前的执行状态'," + + "\n`start_time` datetime DEFAULT NULL COMMENT '该节点活动的开始时间'," + + "\n`end_time` datetime DEFAULT NULL COMMENT '该节点活动的结束时间'," + + "\n`activity_start_time` datetime DEFAULT NULL COMMENT '营销活动的开始时间'," + + "\n`activity_end_time` datetime DEFAULT NULL COMMENT '营销活动的结束时间'," + + "\n`report_start_time` datetime DEFAULT NULL COMMENT '该节点活动效果报告的开始时间'," + + "\n`report_end_time` datetime DEFAULT NULL COMMENT '该节点活动效果报告的结束时间'," + + "\n`cron_rule` varchar(100) DEFAULT NULL COMMENT '周期性营销的时间表达式'," + + "\n`split_rule` varchar(1000) DEFAULT NULL COMMENT '节点拆分规则'," + + "\n`search_json` varchar(5000) DEFAULT NULL COMMENT '索引查找的条件字符串'," + + "\n`filter_json` varchar(1000) DEFAULT NULL COMMENT '各种过滤条件的设置'," + + "\n`buyer_count` int(11) DEFAULT NULL COMMENT '营销的会员数量'," + + "\n`gmt_modified` datetime NOT NULL COMMENT '活动最后修改时间'," + + "\n`gmt_create` datetime NOT NULL COMMENT '活动创建时间'," + + "\nPRIMARY KEY (`id`)," + + "\nKEY `canvas_id` (`canvas_id`)," + + "\nKEY `sid_ty_time` (`sellerId`,`node_type`,`start_time`)," + + "\nCONSTRAINT `t_activity_node_ibfk_1` FOREIGN KEY (`canvas_id`) REFERENCES `t_activity_canvas` (`id`) ON DELETE CASCADE" + + "\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; + + MySqlStatementParser parser = new MySqlStatementParser(sql); + SQLStatement stmt = parser.parseCreateTable(); + + MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); + stmt.accept(visitor); + + System.out.println("Tables : " + visitor.getTables()); + System.out.println("fields : " + visitor.getColumns()); + System.out.println("coditions : " + visitor.getConditions()); + + Assert.assertEquals(2, visitor.getTables().size()); + Assert.assertEquals(24, visitor.getColumns().size()); + Assert.assertEquals(0, visitor.getConditions().size()); + + String output = SQLUtils.toMySqlString(stmt); + Assert.assertEquals("CREATE TABLE `t_activity_node` (" + + "\n\t`id` bigint(20) NOT NULL, " + + "\n\t`sellerId` bigint(20) NULL, " + + "\n\t`canvas_id` bigint(20) NOT NULL COMMENT '画布ID', " + + "\n\t`view_node_id` bigint(20) NOT NULL COMMENT '对应显示的节点id', " + + "\n\t`activity_type` int(11) NOT NULL COMMENT '活动类型', " + + "\n\t`node_type` int(11) NOT NULL COMMENT '节点类型', " + + "\n\t`node_title` varchar(200) NOT NULL COMMENT '节点标题', " + + "\n\t`status` int(11) DEFAULT '0' NOT NULL COMMENT '页面的显示状态', " + + "\n\t`update_status` int(11) NULL COMMENT '节点创建后的修改状态', " + + "\n\t`execute_status` int(11) NULL COMMENT '节点当前的执行状态', " + + "\n\t`start_time` datetime NULL COMMENT '该节点活动的开始时间', " + + "\n\t`end_time` datetime NULL COMMENT '该节点活动的结束时间', " + + "\n\t`activity_start_time` datetime NULL COMMENT '营销活动的开始时间', " + + "\n\t`activity_end_time` datetime NULL COMMENT '营销活动的结束时间', " + + "\n\t`report_start_time` datetime NULL COMMENT '该节点活动效果报告的开始时间', " + + "\n\t`report_end_time` datetime NULL COMMENT '该节点活动效果报告的结束时间', " + + "\n\t`cron_rule` varchar(100) NULL COMMENT '周期性营销的时间表达式', " + + "\n\t`split_rule` varchar(1000) NULL COMMENT '节点拆分规则', " + + "\n\t`search_json` varchar(5000) NULL COMMENT '索引查找的条件字符串', " + + "\n\t`filter_json` varchar(1000) NULL COMMENT '各种过滤条件的设置', " + + "\n\t`buyer_count` int(11) NULL COMMENT '营销的会员数量', " + + "\n\t`gmt_modified` datetime NOT NULL COMMENT '活动最后修改时间', " + + "\n\t`gmt_create` datetime NOT NULL COMMENT '活动创建时间', " + + "\n\tPRIMARY KEY (`id`), " + + "\n\tKEY `canvas_id` (`canvas_id`), " + + "\n\tKEY `sid_ty_time` (`sellerId`, `node_type`, `start_time`), " + + "\n\tCONSTRAINT `t_activity_node_ibfk_1` FOREIGN KEY (`canvas_id`) REFERENCES `t_activity_canvas` (`id`) ON DELETE CASCADE" + + "\n) ENGINE = InnoDB CHARSET = utf8", output); + + } + }