From 6d6aa8eda159e58b53fc15efedd0e746d564f498 Mon Sep 17 00:00:00 2001 From: wenshao Date: Fri, 24 Mar 2017 21:06:38 +0800 Subject: [PATCH] improved support hints. --- .../ast/statement/SQLCharacterDataType.java | 13 +++ .../sql/ast/statement/SQLConstraintImpl.java | 15 +++ .../ast/statement/SQLNotNullConstraint.java | 5 + .../mysql/visitor/MySqlOutputVisitor.java | 16 +++ .../druid/sql/parser/SQLExprParser.java | 16 ++- .../sql/visitor/SQLASTOutputVisitor.java | 19 +++ .../mysql/create/MySqlCreateTableTest75.java | 110 ++++++++++++++++++ 7 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/alibaba/druid/bvt/sql/mysql/create/MySqlCreateTableTest75.java diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLCharacterDataType.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLCharacterDataType.java index dd85cc1059..39a16b2ab3 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLCharacterDataType.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLCharacterDataType.java @@ -15,9 +15,12 @@ */ package com.alibaba.druid.sql.ast.statement; +import com.alibaba.druid.sql.ast.SQLCommentHint; import com.alibaba.druid.sql.ast.SQLDataTypeImpl; import com.alibaba.druid.sql.visitor.SQLASTVisitor; +import java.util.List; + public class SQLCharacterDataType extends SQLDataTypeImpl { private String charSetName; @@ -26,6 +29,8 @@ public class SQLCharacterDataType extends SQLDataTypeImpl { private String charType; private boolean hasBinary; + public List hints; + public final static String CHAR_TYPE_BYTE = "BYTE"; public final static String CHAR_TYPE_CHAR = "CHAR"; @@ -65,6 +70,14 @@ public class SQLCharacterDataType extends SQLDataTypeImpl { this.charType = charType; } + public List getHints() { + return hints; + } + + public void setHints(List hints) { + this.hints = hints; + } + @Override protected void accept0(SQLASTVisitor visitor) { if (visitor.visit(this)) { diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLConstraintImpl.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLConstraintImpl.java index b5abb63933..8a4a10f996 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLConstraintImpl.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLConstraintImpl.java @@ -15,18 +15,33 @@ */ package com.alibaba.druid.sql.ast.statement; +import com.alibaba.druid.sql.ast.SQLCommentHint; import com.alibaba.druid.sql.ast.SQLName; import com.alibaba.druid.sql.ast.SQLObjectImpl; +import java.util.List; + public abstract class SQLConstraintImpl extends SQLObjectImpl implements SQLConstraint { private SQLName name; private Boolean enable; + public List hints; + public SQLConstraintImpl(){ } + + public List getHints() { + return hints; + } + + public void setHints(List hints) { + this.hints = hints; + } + + public SQLName getName() { return name; } diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLNotNullConstraint.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLNotNullConstraint.java index 4ae0b6e674..cb1a0d52ef 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLNotNullConstraint.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLNotNullConstraint.java @@ -15,10 +15,15 @@ */ package com.alibaba.druid.sql.ast.statement; +import com.alibaba.druid.sql.ast.SQLCommentHint; import com.alibaba.druid.sql.visitor.SQLASTVisitor; +import java.util.List; + public class SQLNotNullConstraint extends SQLConstraintImpl implements SQLColumnConstraint { + + public SQLNotNullConstraint(){ } 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 4e7ea72b4e..e722b515c2 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 @@ -405,6 +405,14 @@ public class MySqlOutputVisitor extends SQLASTOutputVisitor implements MySqlASTV print0(charType.getCollate()); } } + + List hints = ((SQLCharacterDataType) x).hints; + if (hints != null) { + print(' '); + for (SQLCommentHint hint : hints) { + hint.accept(this); + } + } } return false; } @@ -434,6 +442,14 @@ public class MySqlOutputVisitor extends SQLASTOutputVisitor implements MySqlASTV print0(x.getCollate()); } + List hints = ((SQLCharacterDataType) x).hints; + if (hints != null) { + print(' '); + for (SQLCommentHint hint : hints) { + hint.accept(this); + } + } + return false; } diff --git a/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java b/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java index 503f811943..5f137c47eb 100644 --- a/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java +++ b/src/main/java/com/alibaba/druid/sql/parser/SQLExprParser.java @@ -1758,7 +1758,14 @@ public class SQLExprParser extends SQLParser { accept(Token.RPAREN); } - return parseCharTypeRest(charType); + charType = (SQLCharacterDataType) parseCharTypeRest(charType); + + if (lexer.token() == Token.HINT) { + List hints = this.parseHints(); + charType.setHints(hints); + } + + return charType; } if ("character".equalsIgnoreCase(typeName) && "varying".equalsIgnoreCase(lexer.stringVal())) { @@ -1867,7 +1874,12 @@ public class SQLExprParser extends SQLParser { if (lexer.token() == Token.NOT) { lexer.nextToken(); accept(Token.NULL); - column.addConstraint(new SQLNotNullConstraint()); + SQLNotNullConstraint notNull = new SQLNotNullConstraint(); + if (lexer.token() == Token.HINT) { + List hints = this.parseHints(); + notNull.setHints(hints); + } + column.addConstraint(notNull); return parseColumnRest(column); } diff --git a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java index 9002e95fd5..61e1a921e3 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -537,6 +537,15 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Paramet public boolean visit(SQLCharacterDataType x) { visit((SQLDataType) x); + + List hints = ((SQLCharacterDataType) x).hints; + if (hints != null) { + print(' '); + for (SQLCommentHint hint : hints) { + hint.accept(this); + } + } + return false; } @@ -1678,6 +1687,16 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements Paramet print(' '); } print0(ucase ? "NOT NULL" : "not null"); + + List hints = x.hints; + + if (hints != null) { + print(' '); + for (SQLCommentHint hint : hints) { + hint.accept(this); + } + } + return false; } diff --git a/src/test/java/com/alibaba/druid/bvt/sql/mysql/create/MySqlCreateTableTest75.java b/src/test/java/com/alibaba/druid/bvt/sql/mysql/create/MySqlCreateTableTest75.java new file mode 100644 index 0000000000..3716785df8 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/mysql/create/MySqlCreateTableTest75.java @@ -0,0 +1,110 @@ +/* + * 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.mysql.create; + +import com.alibaba.druid.sql.MysqlTest; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor; +import com.alibaba.druid.stat.TableStat.Column; +import org.junit.Assert; +import org.junit.Test; + +public class MySqlCreateTableTest75 extends MysqlTest { + + @Test + public void test_one() throws Exception { + String sql = "CREATE TABLE `sys_msg_entry_0320` (\n" + + " `provider_dsp_name` varchar(256) /*!50616 COLUMN_FORMAT COMPRESSED */ DEFAULT NULL COMMENT '消息提供者的显示名,提供给一些需要统一发送名称的系统消息',\n" + + " `template_data` varchar(2048) NOT NULL /*!50616 COLUMN_FORMAT COMPRESSED */ COMMENT '模板渲染时使用的数据,key-value对',\n" + + " `template_merge_data` varchar(512) /*!50616 COLUMN_FORMAT COMPRESSED */ DEFAULT NULL COMMENT '对于需要合并的消息,被合并的参数放在此处理',\n" + + " `expiration_date` datetime NOT NULL COMMENT '失效日期,精度到天,查询和任务处理时使用',\n" + + " `merge_key` varchar(128) DEFAULT NULL COMMENT '消息合并主键,合并成功的消息,不参与计数',\n" + + " `out_id` varchar(256) /*!50616 COLUMN_FORMAT COMPRESSED */ DEFAULT NULL COMMENT '外部关联源的id',\n" + + " `number_expiration_date` datetime DEFAULT NULL COMMENT '计数过期时间',\n" + + " `hidden` int(11) DEFAULT '0' COMMENT '标记是否在吊顶展示,1表示隐藏,0表示展示',\n" + + " `popup` tinyint(3) unsigned DEFAULT '0' COMMENT '表示消息提醒方式:0-数字提醒,1-layer,2-popup',\n" + + " `tag_id` bigint(20) unsigned DEFAULT NULL COMMENT 'TAG标志',\n" + + " `attribute` varchar(512) /*!50616 COLUMN_FORMAT COMPRESSED */ DEFAULT NULL COMMENT '消息的一些属性',\n" + + " `original_msg_ids` varchar(1024) /*!50616 COLUMN_FORMAT COMPRESSED */ DEFAULT NULL COMMENT '原始消息ID,多个id用半角逗号隔开',\n" + + " PRIMARY KEY (`id`),\n" + + " KEY `idx_expiration_date` (`expiration_date`),\n" + + " KEY `k_rid_hidd_cid` (`receiver_id`,`hidden`,`cat_id`,`expiration_date`),\n" + + " KEY `k_rid_aid_tid` (`receiver_id`,`app_id`,`type_id`,`expiration_date`),\n" + + " KEY `k_rid_stat_popup` (`receiver_id`,`status`,`popup`,`expiration_date`),\n" + + " KEY `k_tid` (`tag_id`)\n" + + ") ENGINE=InnoDB AUTO_INCREMENT=167279613030 DEFAULT CHARSET=gbk COMMENT='消息实体表'"; + + MySqlStatementParser parser = new MySqlStatementParser(sql); + SQLStatement stmt = parser.parseCreateTable(); + + MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); + stmt.accept(visitor); + + Column column = visitor.getColumn("sys_msg_entry_0320", "provider_dsp_name"); + Assert.assertNotNull(column); + Assert.assertEquals("varchar", column.getDataType()); + + { + String output = SQLUtils.toMySqlString(stmt); + Assert.assertEquals("CREATE TABLE `sys_msg_entry_0320` (\n" + + "\t`provider_dsp_name` varchar(256) /*!50616 COLUMN_FORMAT COMPRESSED */ DEFAULT NULL COMMENT '消息提供者的显示名,提供给一些需要统一发送名称的系统消息', \n" + + "\t`template_data` varchar(2048) NOT NULL /*!50616 COLUMN_FORMAT COMPRESSED */ COMMENT '模板渲染时使用的数据,key-value对', \n" + + "\t`template_merge_data` varchar(512) /*!50616 COLUMN_FORMAT COMPRESSED */ DEFAULT NULL COMMENT '对于需要合并的消息,被合并的参数放在此处理', \n" + + "\t`expiration_date` datetime NOT NULL COMMENT '失效日期,精度到天,查询和任务处理时使用', \n" + + "\t`merge_key` varchar(128) DEFAULT NULL COMMENT '消息合并主键,合并成功的消息,不参与计数', \n" + + "\t`out_id` varchar(256) /*!50616 COLUMN_FORMAT COMPRESSED */ DEFAULT NULL COMMENT '外部关联源的id', \n" + + "\t`number_expiration_date` datetime DEFAULT NULL COMMENT '计数过期时间', \n" + + "\t`hidden` int(11) DEFAULT '0' COMMENT '标记是否在吊顶展示,1表示隐藏,0表示展示', \n" + + "\t`popup` tinyint(3) UNSIGNED DEFAULT '0' COMMENT '表示消息提醒方式:0-数字提醒,1-layer,2-popup', \n" + + "\t`tag_id` bigint(20) UNSIGNED DEFAULT NULL COMMENT 'TAG标志', \n" + + "\t`attribute` varchar(512) /*!50616 COLUMN_FORMAT COMPRESSED */ DEFAULT NULL COMMENT '消息的一些属性', \n" + + "\t`original_msg_ids` varchar(1024) /*!50616 COLUMN_FORMAT COMPRESSED */ DEFAULT NULL COMMENT '原始消息ID,多个id用半角逗号隔开', \n" + + "\tPRIMARY KEY (`id`), \n" + + "\tKEY `idx_expiration_date` (`expiration_date`), \n" + + "\tKEY `k_rid_hidd_cid` (`receiver_id`, `hidden`, `cat_id`, `expiration_date`), \n" + + "\tKEY `k_rid_aid_tid` (`receiver_id`, `app_id`, `type_id`, `expiration_date`), \n" + + "\tKEY `k_rid_stat_popup` (`receiver_id`, `status`, `popup`, `expiration_date`), \n" + + "\tKEY `k_tid` (`tag_id`)\n" + + ") ENGINE = InnoDB AUTO_INCREMENT = 167279613030 CHARSET = gbk COMMENT = '消息实体表'", output); + } + + { + String output = SQLUtils.toMySqlString(stmt, SQLUtils.DEFAULT_LCASE_FORMAT_OPTION); + Assert.assertEquals("create table `sys_msg_entry_0320` (\n" + + "\t`provider_dsp_name` varchar(256) /*!50616 COLUMN_FORMAT COMPRESSED */ default null comment '消息提供者的显示名,提供给一些需要统一发送名称的系统消息', \n" + + "\t`template_data` varchar(2048) not null /*!50616 COLUMN_FORMAT COMPRESSED */ comment '模板渲染时使用的数据,key-value对', \n" + + "\t`template_merge_data` varchar(512) /*!50616 COLUMN_FORMAT COMPRESSED */ default null comment '对于需要合并的消息,被合并的参数放在此处理', \n" + + "\t`expiration_date` datetime not null comment '失效日期,精度到天,查询和任务处理时使用', \n" + + "\t`merge_key` varchar(128) default null comment '消息合并主键,合并成功的消息,不参与计数', \n" + + "\t`out_id` varchar(256) /*!50616 COLUMN_FORMAT COMPRESSED */ default null comment '外部关联源的id', \n" + + "\t`number_expiration_date` datetime default null comment '计数过期时间', \n" + + "\t`hidden` int(11) default '0' comment '标记是否在吊顶展示,1表示隐藏,0表示展示', \n" + + "\t`popup` tinyint(3) unsigned default '0' comment '表示消息提醒方式:0-数字提醒,1-layer,2-popup', \n" + + "\t`tag_id` bigint(20) unsigned default null comment 'TAG标志', \n" + + "\t`attribute` varchar(512) /*!50616 COLUMN_FORMAT COMPRESSED */ default null comment '消息的一些属性', \n" + + "\t`original_msg_ids` varchar(1024) /*!50616 COLUMN_FORMAT COMPRESSED */ default null comment '原始消息ID,多个id用半角逗号隔开', \n" + + "\tprimary key (`id`), \n" + + "\tkey `idx_expiration_date` (`expiration_date`), \n" + + "\tkey `k_rid_hidd_cid` (`receiver_id`, `hidden`, `cat_id`, `expiration_date`), \n" + + "\tkey `k_rid_aid_tid` (`receiver_id`, `app_id`, `type_id`, `expiration_date`), \n" + + "\tkey `k_rid_stat_popup` (`receiver_id`, `status`, `popup`, `expiration_date`), \n" + + "\tkey `k_tid` (`tag_id`)\n" + + ") engine = InnoDB auto_increment = 167279613030 charset = gbk comment = '消息实体表'", output); + } + } +}