diff --git a/advisor/heuristic.go b/advisor/heuristic.go index f06bc30f7fb3efc6c860dd4aa72a1638fe0a8183..4d2755196adb0c8678455e1ea28c867a789a073d 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 } @@ -2729,8 +2729,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 + } } } } @@ -2807,7 +2813,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"] @@ -2830,7 +2836,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"] @@ -3157,7 +3163,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 } @@ -3173,6 +3180,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 } @@ -3187,6 +3204,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 } @@ -3391,7 +3417,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"] @@ -3410,7 +3436,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 8b57ace8a166a5f5584278d364da911598b18903..fdbf10ae3d95c23a50a5af360a2b085816af59f1 100644 --- a/advisor/heuristic_test.go +++ b/advisor/heuristic_test.go @@ -3265,17 +3265,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/ast/testdata/TestSplitStatement.golden b/ast/testdata/TestSplitStatement.golden index 15e7e66158e03eeacf6140deec60b158792e1958..9ad59bac1beb569f741216722c3230b97fc5b1be 100644 --- a/ast/testdata/TestSplitStatement.golden +++ b/ast/testdata/TestSplitStatement.golden @@ -40,6 +40,7 @@ tb; 20 select /*!50000 1,*/ 1; 21 UPDATE xxx SET c1=' LOGGER.error(""); }' WHERE id = 2 ; 22 UPDATE `xxx` SET aaa='a;' WHERE `id` = 15; +23 UPDATE `xxx` SET aaa='a -- b' WHERE `id` = 15; 0 select * from test\G 1 select 'hello\Gworld', col from test\G 2 -- select * from test\Ghello diff --git a/ast/token.go b/ast/token.go index cc9306590221eda17c8d965b789a3223dc8b302d..432827ca40dba7a3f705a0c7f0658f9281fc9872 100644 --- a/ast/token.go +++ b/ast/token.go @@ -862,12 +862,12 @@ func SplitStatement(buf []byte, delimiter []byte) (string, string, []byte) { b := buf[i] // single line comment if b == '-' { - if i+2 < len(buf) && buf[i+1] == '-' && buf[i+2] == ' ' { + if !quoted && i+2 < len(buf) && buf[i+1] == '-' && buf[i+2] == ' ' { singleLineComment = true i = i + 2 continue } - if i+2 < len(buf) && i == 0 && buf[i+1] == '-' && (buf[i+2] == '\n' || buf[i+2] == '\r') { + if !quoted && i+2 < len(buf) && i == 0 && buf[i+1] == '-' && (buf[i+2] == '\n' || buf[i+2] == '\r') { sql = "--\n" break } diff --git a/ast/token_test.go b/ast/token_test.go index fe7b4051dedbb95461ec1ecaba608765dfc19d1a..5b3e992f23c309b651fab7a8fb674e7a2a21113c 100644 --- a/ast/token_test.go +++ b/ast/token_test.go @@ -173,7 +173,8 @@ select col from tb; []byte(`select /*!50000 1,*/ 1;`), // 20 []byte(`UPDATE xxx SET c1=' LOGGER.error(""); }' WHERE id = 2 ;`), // 21 []byte("UPDATE `xxx` SET aaa='a;' WHERE `id` = 15;"), // 22 - // []byte(`/* comment here */ SET MAX_JOIN_SIZE=#`), // 23 + []byte("UPDATE `xxx` SET aaa='a -- b' WHERE `id` = 15; UPDATE `xxx` SET aaa='c -- d' WHERE `id` = 16;"), // 23 + // []byte(`/* comment here */ SET MAX_JOIN_SIZE=#`), // 24 } // \G 分隔符 buf2s := [][]byte{ 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 }