提交 1ab0ac83 编写于 作者: martianzhang's avatar martianzhang

Merge branch 'dev' of github.com:XiaoMi/soar into dev

......@@ -17,9 +17,9 @@ VERSION_TAG := $(shell git describe --tags --always)
VERSION_VERSION := $(shell git log --date=iso --pretty=format:"%cd" -1) $(VERSION_TAG)
VERSION_COMPILE := $(shell date +"%F %T %z") by $(shell go version)
VERSION_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
VERSION_GIT_DIRTY := $(shell git diff --no-ext-diff 2>/dev/null | wc -l | awk '{print 1}')
VERSION_GIT_DIRTY := $(shell git diff --no-ext-diff 2>/dev/null | wc -l | awk '{print $1}')
VERSION_DEV_PATH:= $(shell pwd)
LDFLAGS=-ldflags="-s -w -X 'github.com/XiaoMi/soar/common.Version=$(VERSION_VERSION)' -X 'github.com/XiaoMi/soar/common.Compile=$(VERSION_COMPILE)' -X 'github.com/XiaoMi/soar/common.Branch=$(VERSION_BRANCH)' -X github.com/XiaoMi/soar/common.GitDirty=$(VERSION_GIT_DIRTY) -X github.com/XiaoMi/soar/common.DevPath=$(VERSION_DEV_PATH)"
LDFLAGS=-ldflags="-s -w -X 'github.com/XiaoMi/soar/common.Version=$(VERSION_VERSION)' -X 'github.com/XiaoMi/soar/common.Compile=$(VERSION_COMPILE)' -X 'github.com/XiaoMi/soar/common.Branch=$(VERSION_BRANCH)' -X 'github.com/XiaoMi/soar/common.GitDirty=$(VERSION_GIT_DIRTY)' -X 'github.com/XiaoMi/soar/common.DevPath=$(VERSION_DEV_PATH)'"
# These are the values we want to pass for VERSION and BUILD
BUILD_TIME=`date +%Y%m%d%H%M`
......
......@@ -279,7 +279,7 @@ func (idxAdv *IndexAdvisor) RuleImplicitConversion() Rule {
continue
}
// 检查排序排序不一致导致的隐式数据转换
// 检查 collation 排序不一致导致的隐式数据转换
common.Log.Debug("Collation: `%s`.`%s` (%s) VS `%s`.`%s` (%s)",
colList[0].Table, colList[0].Name, colList[0].Collation,
colList[1].Table, colList[1].Name, colList[1].Collation)
......@@ -322,6 +322,7 @@ func (idxAdv *IndexAdvisor) RuleImplicitConversion() Rule {
isCovered := true
if tps, ok := typMap[val.Type]; ok {
for _, tp := range tps {
// colList[0].DataType, eg. year(4)
if strings.HasPrefix(colList[0].DataType, tp) {
isCovered = false
}
......@@ -339,6 +340,18 @@ func (idxAdv *IndexAdvisor) RuleImplicitConversion() Rule {
common.Log.Debug("Implicit data type conversion: %s", c)
content = append(content, c)
} else {
// 检查时间格式,如:"", "2020-0a-01"
switch strings.Split(colList[0].DataType, "(")[0] {
case "date", "time", "datetime", "timestamp", "year":
if !timeFormatCheck(string(val.Val)) {
c := fmt.Sprintf("%s 表中列 %s 的时间格式错误,%s。", colList[0].Table, colList[0].Name, string(val.Val))
common.Log.Debug("Implicit data type conversion: %s", c)
content = append(content, c)
}
// TODO: 各种数据类型格式检查
default:
}
}
}
......@@ -355,6 +368,15 @@ func (idxAdv *IndexAdvisor) RuleImplicitConversion() Rule {
return rule
}
// timeFormatCheck 时间格式检查,格式正确返回 true,格式错误返回 false
func timeFormatCheck(t string) bool {
// 不允许为空,但允许时间前后有空格
t = strings.TrimSpace(t)
// 仅允许 数字、减号、冒号、空格
allowChars := regexp.MustCompile(`^[\-0-9: ]+$`)
return allowChars.MatchString(t)
}
// RuleNoWhere CLA.001 & CLA.014 & CLA.015
func (q *Query4Audit) RuleNoWhere() Rule {
var rule = q.RuleOK()
......@@ -832,7 +854,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 +877,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
}
......@@ -1253,20 +1275,66 @@ func (q *Query4Audit) RuleImpossibleWhere() Rule {
// RuleMeaninglessWhere RES.007
func (q *Query4Audit) RuleMeaninglessWhere() Rule {
var rule = q.RuleOK()
// SELECT * FROM tb WHERE 1
var where *sqlparser.Where
switch n := q.Stmt.(type) {
case *sqlparser.Select:
if n.Where != nil {
switch n.Where.Expr.(type) {
case *sqlparser.SQLVal:
where = n.Where
case *sqlparser.Update:
where = n.Where
case *sqlparser.Delete:
where = n.Where
}
if where != nil {
switch v := where.Expr.(type) {
// WHERE 1
case *sqlparser.SQLVal:
switch string(v.Val) {
case "0", "false":
default:
rule = HeuristicRules["RES.007"]
return rule
}
// WHERE true
case sqlparser.BoolVal:
if v {
rule = HeuristicRules["RES.007"]
return rule
}
}
}
// 1=1, 0=0
err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
switch n := node.(type) {
// WHERE id = 1 or 2
case *sqlparser.OrExpr:
// right always true
switch v := n.Right.(type) {
case *sqlparser.SQLVal:
switch string(v.Val) {
case "0", "false":
default:
rule = HeuristicRules["RES.007"]
}
case sqlparser.BoolVal:
if v {
rule = HeuristicRules["RES.007"]
}
}
// left always true
switch v := n.Left.(type) {
case *sqlparser.SQLVal:
switch string(v.Val) {
case "0", "false":
default:
rule = HeuristicRules["RES.007"]
}
case sqlparser.BoolVal:
if v {
rule = HeuristicRules["RES.007"]
}
}
// 1=1, 0=0
case *sqlparser.ComparisonExpr:
factor := false
switch n.Operator {
......@@ -1300,6 +1368,12 @@ func (q *Query4Audit) RuleMeaninglessWhere() Rule {
if (bytes.Equal(left, right) && !factor) || (!bytes.Equal(left, right) && factor) {
rule = HeuristicRules["RES.007"]
}
// TODO:
// 2 > 1
// true = 1
// false != 1
return false, nil
}
return true, nil
......@@ -2681,8 +2755,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
}
}
}
}
......@@ -2759,7 +2839,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"]
......@@ -2782,7 +2862,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"]
......@@ -3109,7 +3189,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
}
......@@ -3125,6 +3206,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
}
......@@ -3139,6 +3230,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
}
......@@ -3343,7 +3443,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"]
......@@ -3362,7 +3462,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"]
......
......@@ -150,6 +150,31 @@ func TestRuleEqualLike(t *testing.T) {
common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}
// ARG.003
// TODO:
func TestTimeFormatError(t *testing.T) {
rightTimes := []string{
`2020-01-01`,
}
for _, rt := range rightTimes {
if !timeFormatCheck(rt) {
t.Error("wrong time format")
}
}
wrongTimes := []string{
``, // 空时间
`2020-01-01 abc`, // 含英文字符
`2020–02-15 23:59:59`, // 2020 后面的不是减号,是个 连接符
}
for _, wt := range wrongTimes {
if timeFormatCheck(wt) {
t.Error("wrong time format")
}
}
}
// CLA.001
func TestRuleNoWhere(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
......@@ -931,8 +956,15 @@ func TestRuleMeaninglessWhere(t *testing.T) {
"select * from tbl where 'a' limit 1;",
"select * from tbl where 1;",
"select * from tbl where 1 limit 1;",
"select * from tbl where id = 1 or 2;",
"select * from tbl where true;",
"select * from tbl where 'true';",
},
{
"select * from tbl where false;",
"select * from tbl where 'false';",
"select * from tbl where 0;",
"select * from tbl where '0';",
"select * from tbl where 2 = 1;",
"select * from tbl where 'b' = 'a';",
},
......@@ -3258,17 +3290,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;",
},
}
......
......@@ -531,8 +531,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,
},
......@@ -556,8 +556,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,
},
......
......@@ -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
......
......@@ -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
......
......@@ -45,7 +45,6 @@ const (
var maxCachekeySize = 15
var cacheHits int
var cacheMisses int
var tokenCache map[string]Token
var tokenBoundaries = []string{
// multi character
......@@ -790,7 +789,7 @@ func Tokenize(sql string) []Token {
var token Token
var tokenLength int
var tokens []Token
tokenCache = make(map[string]Token)
tokenCache := make(map[string]Token)
// Used to make sure the string keeps shrinking on each iteration
oldStringLen := len(sql) + 1
......@@ -863,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
}
......
......@@ -169,11 +169,12 @@ select col from tb where col = 1;`), // 17
select col from tb;
select col from tb;
`), // 18
[]byte(`INSERT /*+ SET_VAR(foreign_key_checks=OFF) */ INTO t2 VALUES(2);`), // 19
[]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(`INSERT /*+ SET_VAR(foreign_key_checks=OFF) */ INTO t2 VALUES(2);`), // 19
[]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("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{
......
......@@ -152,6 +152,7 @@ type ExplainJSONNestedLoop struct {
type ExplainJSONBufferResult struct {
UsingTemporaryTable bool `json:"using_temporary_table"`
NestedLoop []ExplainJSONNestedLoop `json:"nested_loop"`
Table ExplainJSONTable `json:"table"`
}
// ExplainJSONSubqueries JSON
......@@ -177,15 +178,17 @@ type ExplainJSONDuplicatesRemoval struct {
UsingFilesort bool `json:"using_filesort"`
BufferResult ExplainJSONBufferResult `json:"buffer_result"`
GroupingOperation ExplainJSONGroupingOperation `json:"grouping_operation"`
Table ExplainJSONTable `json:"table"`
}
// ExplainJSONOrderingOperation JSON
type ExplainJSONOrderingOperation struct {
UsingFilesort bool `json:"using_filesort"`
Table ExplainJSONTable `json:"table"`
DuplicatesRemoval ExplainJSONDuplicatesRemoval `json:"duplicates_removal"`
GroupingOperation ExplainJSONGroupingOperation `json:"grouping_operation"`
OrderbySubqueries []ExplainJSONSubqueries `json:"order_by_subqueries"`
UsingFilesort bool `json:"using_filesort"`
Table ExplainJSONTable `json:"table"`
DuplicatesRemoval ExplainJSONDuplicatesRemoval `json:"duplicates_removal"`
GroupingOperation ExplainJSONGroupingOperation `json:"grouping_operation"`
OrderbySubqueries []ExplainJSONSubqueries `json:"order_by_subqueries"`
OptimizedAwaySubqueries []ExplainJSONSubqueries `json:"optimized_away_subqueries"`
}
// ExplainJSONQueryBlock JSON
......
......@@ -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
}
......
......@@ -202,6 +202,16 @@ INSERT INTO tb (a) VALUES (1), (2)
```sql
CREATE TABLE tb (a varchar(10) default '“”'
```
## IN 条件中存在列名,可能导致数据匹配范围扩大
* **Item**:ARG.014
* **Severity**:L4
* **Content**:如:delete from t where id in(1, 2, id) 可能会导致全表数据误删除。请仔细检查 IN 条件的正确性。
* **Case**:
```sql
select id from t where id in(1, 2, id)
```
## 最外层 SELECT 未指定 WHERE 条件
* **Item**:CLA.001
......@@ -472,11 +482,11 @@ create table tab1(status ENUM('new','in progress','fixed'))
```sql
select c1,c2,c3 from tbl where c4 is null or c4 <> 1
```
## BLOB 和 TEXT 类型的字段不建议设置为 NOT NULL
## TEXT、BLOB 和 JSON 类型的字段不建议设置为 NOT NULL
* **Item**:COL.012
* **Severity**:L5
* **Content**:BLOB 和 TEXT 类型的字段无法指定非 NULL 的默认值,如果添加了 NOT NULL 限制,写入数据时又未对该字段指定值可能导致写入失败。
* **Content**:TEXT、BLOB 和 JSON 类型的字段无法指定非 NULL 的默认值,如果添加了 NOT NULL 限制,写入数据时又未对该字段指定值可能导致写入失败。
* **Case**:
```sql
......@@ -502,11 +512,11 @@ CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);
```sql
CREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL)
```
## TEXT 和 BLOB 类型的字段不可指定非 NULL 的默认值
## TEXT、BLOB 和 JSON 类型的字段不可指定非 NULL 的默认值
* **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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册