diff --git a/advisor/heuristic.go b/advisor/heuristic.go index 258e6149a632d5c14dba6a293e4036eed0464bac..78ced371fdb0cb6fdb0ba1216d7bd1c67a94cade 100644 --- a/advisor/heuristic.go +++ b/advisor/heuristic.go @@ -832,7 +832,7 @@ func (q *Query4Audit) RuleAddDefaultValue() Rule { } switch c.Tp.Tp { - case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob: + case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON: colDefault = true } @@ -855,7 +855,7 @@ func (q *Query4Audit) RuleAddDefaultValue() Rule { } switch c.Tp.Tp { - case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob: + case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON: colDefault = true } @@ -2677,8 +2677,14 @@ func (q *Query4Audit) RuleAlterCharset() Rule { for _, option := range spec.Options { if option.Tp == tidb.TableOptionCharset || option.Tp == tidb.TableOptionCollate { - rule = HeuristicRules["ALT.001"] - break + //增加CONVERT TO的判断 + convertReg, _ := regexp.Compile("convert to") + if convertReg.Match([]byte(strings.ToLower(q.Query))) { + break + } else { + rule = HeuristicRules["ALT.001"] + break + } } } } @@ -2755,7 +2761,7 @@ func (q *Query4Audit) RuleBLOBNotNull() Rule { continue } switch col.Tp.Tp { - case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob: + case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON: for _, opt := range col.Options { if opt.Tp == tidb.ColumnOptionNotNull { rule = HeuristicRules["COL.012"] @@ -2778,7 +2784,7 @@ func (q *Query4Audit) RuleBLOBNotNull() Rule { continue } switch col.Tp.Tp { - case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob: + case mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeJSON: for _, opt := range col.Options { if opt.Tp == tidb.ColumnOptionNotNull { rule = HeuristicRules["COL.012"] @@ -3105,7 +3111,8 @@ func (q *Query4Audit) RuleColumnWithCharset() Rule { for _, tk := range tks { if tk.Type == ast.TokenTypeWord { switch strings.TrimSpace(strings.ToLower(tk.Val)) { - case "national", "nvarchar", "nchar", "nvarchar(", "nchar(", "character": + //character移到后面检查 + case "national", "nvarchar", "nchar", "nvarchar(", "nchar(": rule = HeuristicRules["COL.014"] return rule } @@ -3121,6 +3128,16 @@ func (q *Query4Audit) RuleColumnWithCharset() Rule { continue } if col.Tp.Charset != "" || col.Tp.Collate != "" { + if col.Tp.Charset == "binary" || col.Tp.Collate == "binary" { + continue + } else { + rule = HeuristicRules["COL.014"] + break + } + } + //在这里检查character + characterReg, _ := regexp.Compile("character set") + if characterReg.Match([]byte(strings.ToLower(q.Query))) { rule = HeuristicRules["COL.014"] break } @@ -3135,6 +3152,15 @@ func (q *Query4Audit) RuleColumnWithCharset() Rule { continue } if col.Tp.Charset != "" || col.Tp.Collate != "" { + if col.Tp.Charset == "binary" || col.Tp.Collate == "binary" { + continue + } else { + rule = HeuristicRules["COL.014"] + break + } + } + characterReg, _ := regexp.Compile("character set") + if characterReg.Match([]byte(strings.ToLower(q.Query))) { rule = HeuristicRules["COL.014"] break } @@ -3339,7 +3365,7 @@ func (q *Query4Audit) RuleBlobDefaultValue() Rule { continue } switch col.Tp.Tp { - case mysql.TypeBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob, mysql.TypeLongBlob: + case mysql.TypeBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob, mysql.TypeLongBlob, mysql.TypeJSON: for _, opt := range col.Options { if opt.Tp == tidb.ColumnOptionDefaultValue && opt.Expr.GetType().Tp != mysql.TypeNull { rule = HeuristicRules["COL.015"] @@ -3358,7 +3384,7 @@ func (q *Query4Audit) RuleBlobDefaultValue() Rule { continue } switch col.Tp.Tp { - case mysql.TypeBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob, mysql.TypeLongBlob: + case mysql.TypeBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob, mysql.TypeLongBlob, mysql.TypeJSON: for _, opt := range col.Options { if opt.Tp == tidb.ColumnOptionDefaultValue && opt.Expr.GetType().Tp != mysql.TypeNull { rule = HeuristicRules["COL.015"] diff --git a/advisor/heuristic_test.go b/advisor/heuristic_test.go index a5a0a8ef95fc633042c89e9a2525c1afb5930e0d..afac99a0eeb3a423985e4b1e57d28660b78d12f9 100644 --- a/advisor/heuristic_test.go +++ b/advisor/heuristic_test.go @@ -3258,17 +3258,24 @@ func TestRuleBlobDefaultValue(t *testing.T) { sqls := [][]string{ { "CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`));", + "CREATE TABLE `tb` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` json NOT NULL DEFAULT '', PRIMARY KEY (`id`));", "alter table `tb` add column `c` blob NOT NULL DEFAULT '';", + "alter table `tb` add column `c` json NOT NULL DEFAULT '';", }, { "CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL, PRIMARY KEY (`id`));", + "CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` json NOT NULL, PRIMARY KEY (`id`));", "CREATE TABLE `tb` (`col` text NOT NULL);", "alter table `tb` add column `c` blob NOT NULL;", + "alter table `tb` add column `c` json NOT NULL;", "ALTER TABLE tb ADD COLUMN a BLOB DEFAULT NULL", + "ALTER TABLE tb ADD COLUMN a JSON DEFAULT NULL", "CREATE TABLE tb ( a BLOB DEFAULT NULL)", + "CREATE TABLE tb ( a JSON DEFAULT NULL)", "alter TABLE `tbl` add column `c` longblob;", "alter TABLE `tbl` add column `c` text;", "alter TABLE `tbl` add column `c` blob;", + "alter TABLE `tbl` add column `c` json;", }, } diff --git a/advisor/rules.go b/advisor/rules.go index f7f933430ec599a800e141b395df5d8572eae0cb..fb542c6be8c4516b33c736e9c05014dbda4926a4 100644 --- a/advisor/rules.go +++ b/advisor/rules.go @@ -523,8 +523,8 @@ func init() { "COL.012": { Item: "COL.012", Severity: "L5", - Summary: "BLOB 和 TEXT 类型的字段不建议设置为 NOT NULL", - Content: `BLOB 和 TEXT 类型的字段无法指定非 NULL 的默认值,如果添加了 NOT NULL 限制,写入数据时又未对该字段指定值可能导致写入失败。`, + Summary: "TEXT、BLOB 和 JSON 类型的字段不建议设置为 NOT NULL", + Content: `TEXT、BLOB 和 JSON 类型的字段无法指定非 NULL 的默认值,如果添加了 NOT NULL 限制,写入数据时又未对该字段指定值可能导致写入失败。`, Case: "CREATE TABLE `tb`(`c` longblob NOT NULL);", Func: (*Query4Audit).RuleBLOBNotNull, }, @@ -548,8 +548,8 @@ func init() { "COL.015": { Item: "COL.015", Severity: "L4", - Summary: "TEXT 和 BLOB 类型的字段不可指定非 NULL 的默认值", - Content: `MySQL 数据库中 TEXT 和 BLOB 类型的字段不可指定非 NULL 的默认值。TEXT最大长度为2^16-1个字符,MEDIUMTEXT最大长度为2^32-1个字符,LONGTEXT最大长度为2^64-1个字符。`, + Summary: "TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值", + Content: `MySQL 数据库中 TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值。TEXT最大长度为2^16-1个字符,MEDIUMTEXT最大长度为2^32-1个字符,LONGTEXT最大长度为2^64-1个字符。`, Case: "CREATE TABLE `tbl` (`c` blob DEFAULT NULL);", Func: (*Query4Audit).RuleBlobDefaultValue, }, diff --git a/advisor/testdata/TestListHeuristicRules.golden b/advisor/testdata/TestListHeuristicRules.golden index 9731c976ac77046e37fab08c6d254ba2176b6245..7e11549dab5b8070106eb28d09d7ef1ff9ef39b4 100644 --- a/advisor/testdata/TestListHeuristicRules.golden +++ b/advisor/testdata/TestListHeuristicRules.golden @@ -476,7 +476,7 @@ select c1,c2,c3 from tbl where c4 is null or c4 <> 1 * **Item**:COL.012 * **Severity**:L5 -* **Content**:BLOB 和 TEXT 类型的字段无法指定非 NULL 的默认值,如果添加了 NOT NULL 限制,写入数据时又未对该字段指定值可能导致写入失败。 +* **Content**:TEXT、BLOB 和 JSON 类型的字段无法指定非 NULL 的默认值,如果添加了 NOT NULL 限制,写入数据时又未对该字段指定值可能导致写入失败。 * **Case**: ```sql @@ -506,7 +506,7 @@ CREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf * **Item**:COL.015 * **Severity**:L4 -* **Content**:MySQL 数据库中 TEXT 和 BLOB 类型的字段不可指定非 NULL 的默认值。TEXT最大长度为2^16-1个字符,MEDIUMTEXT最大长度为2^32-1个字符,LONGTEXT最大长度为2^64-1个字符。 +* **Content**:MySQL 数据库中 TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值。TEXT最大长度为2^16-1个字符,MEDIUMTEXT最大长度为2^32-1个字符,LONGTEXT最大长度为2^64-1个字符。 * **Case**: ```sql diff --git a/database/show.go b/database/show.go index 573ce274766b69ef66061c7a5bb77f45dc7de27b..b81605e5e831285b9a0a50ab5c687f62baa1778c 100644 --- a/database/show.go +++ b/database/show.go @@ -70,6 +70,15 @@ type tableStatusRow struct { Comment []byte // 注释 } +// 记录去除逗号类型是外健还是分区表 +type deleteComaType int8 + +const ( + _ deleteComaType = iota + CS + PART +) + // newTableStat 构造 table Stat 对象 func newTableStat(tableName string) *TableStatInfo { return &TableStatInfo{ @@ -478,21 +487,37 @@ func (db *Connector) ShowCreateTable(tableName string) (string, error) { if len(lines) > 2 { var noConstraint []string relationReg, _ := regexp.Compile("CONSTRAINT") + partitionReg, _ := regexp.Compile("PARTITIONS") + var DeleteComaT deleteComaType for _, line := range lines[1 : len(lines)-1] { if relationReg.Match([]byte(line)) { + DeleteComaT = CS continue + } else if partitionReg.Match([]byte(line)) { + DeleteComaT = PART } line = strings.TrimSuffix(line, ",") noConstraint = append(noConstraint, line) } // 去除外键语句会使DDL中多一个','导致语法错误,要把多余的逗号去除 - ddl = fmt.Sprint( - lines[0], "\n", - strings.Join(noConstraint, ",\n"), "\n", - lines[len(lines)-1], - ) + // len(lines) > 2的判断方式有问题,如果是分区表也会判断成为外键语句,导致建表语句的逗号错乱 + if DeleteComaT == CS { + ddl = fmt.Sprint( + lines[0], "\n", + strings.Join(noConstraint, ",\n"), "\n", + lines[len(lines)-1], + ) + } else if DeleteComaT == PART { + ddl = fmt.Sprint( + lines[0], "\n", + strings.Join(noConstraint, ",\n"), "\n", + lines[len(lines)-3], + ) + } + } + return ddl, err }