Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
rainie_yuyue
soar
提交
1ab0ac83
S
soar
项目概览
rainie_yuyue
/
soar
与 Fork 源项目一致
Fork自
Xiaomi / soar
通知
1
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
soar
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
1ab0ac83
编写于
1月 22, 2021
作者:
martianzhang
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'dev' of github.com:XiaoMi/soar into dev
上级
47b425c9
3ed71a7d
变更
11
隐藏空白更改
内联
并排
Showing
11 changed file
with
224 addition
and
46 deletion
+224
-46
Makefile
Makefile
+2
-2
advisor/heuristic.go
advisor/heuristic.go
+115
-15
advisor/heuristic_test.go
advisor/heuristic_test.go
+39
-0
advisor/rules.go
advisor/rules.go
+4
-4
advisor/testdata/TestListHeuristicRules.golden
advisor/testdata/TestListHeuristicRules.golden
+2
-2
ast/testdata/TestSplitStatement.golden
ast/testdata/TestSplitStatement.golden
+1
-0
ast/token.go
ast/token.go
+3
-4
ast/token_test.go
ast/token_test.go
+6
-5
database/explain.go
database/explain.go
+8
-5
database/show.go
database/show.go
+30
-5
doc/heuristic.md
doc/heuristic.md
+14
-4
未找到文件。
Makefile
浏览文件 @
1ab0ac83
...
...
@@ -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
`
...
...
advisor/heuristic.go
浏览文件 @
1ab0ac83
...
...
@@ -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"
]
...
...
advisor/heuristic_test.go
浏览文件 @
1ab0ac83
...
...
@@ -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;"
,
},
}
...
...
advisor/rules.go
浏览文件 @
1ab0ac83
...
...
@@ -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
,
},
...
...
advisor/testdata/TestListHeuristicRules.golden
浏览文件 @
1ab0ac83
...
...
@@ -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
...
...
ast/testdata/TestSplitStatement.golden
浏览文件 @
1ab0ac83
...
...
@@ -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
...
...
ast/token.go
浏览文件 @
1ab0ac83
...
...
@@ -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
}
...
...
ast/token_test.go
浏览文件 @
1ab0ac83
...
...
@@ -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
{
...
...
database/explain.go
浏览文件 @
1ab0ac83
...
...
@@ -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
...
...
database/show.go
浏览文件 @
1ab0ac83
...
...
@@ -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
}
...
...
doc/heuristic.md
浏览文件 @
1ab0ac83
...
...
@@ -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.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录