diff --git a/advisor/heuristic.go b/advisor/heuristic.go index 2abee312888dd87a7c687af6dd1d6bccdebbe02e..e5e031b8df842861d1aa058bbd4744cbcb35acd6 100644 --- a/advisor/heuristic.go +++ b/advisor/heuristic.go @@ -1308,6 +1308,42 @@ func (q *Query4Audit) RuleLoadFile() Rule { return rule } +// RuleMultiCompare RES.009 +func (q *Query4Audit) RuleMultiCompare() Rule { + var rule = q.RuleOK() + if q.TiStmt != nil { + for _, tiStmt := range q.TiStmt { + switch node := tiStmt.(type) { + case *tidb.SelectStmt: + switch where := node.Where.(type) { + case *tidb.BinaryOperationExpr: + switch where.L.(type) { + case *tidb.BinaryOperationExpr: + rule = HeuristicRules["RES.009"] + } + } + case *tidb.UpdateStmt: + switch where := node.Where.(type) { + case *tidb.BinaryOperationExpr: + switch where.L.(type) { + case *tidb.BinaryOperationExpr: + rule = HeuristicRules["RES.009"] + } + } + case *tidb.DeleteStmt: + switch where := node.Where.(type) { + case *tidb.BinaryOperationExpr: + switch where.L.(type) { + case *tidb.BinaryOperationExpr: + rule = HeuristicRules["RES.009"] + } + } + } + } + } + return rule +} + // RuleStandardINEQ STA.001 func (q *Query4Audit) RuleStandardINEQ() Rule { var rule = q.RuleOK() diff --git a/advisor/heuristic_test.go b/advisor/heuristic_test.go index dad57401523f6b44c857ad87f1ecb9529a6a59a6..a729e01d4265cc9f99be07c71a62577f76301885 100644 --- a/advisor/heuristic_test.go +++ b/advisor/heuristic_test.go @@ -941,6 +941,46 @@ func TestRuleLoadFile(t *testing.T) { common.Log.Debug("Exiting function: %s", common.GetFunctionName()) } +// RES.009 +func TestRuleMultiCompare(t *testing.T) { + common.Log.Debug("Entering function: %s", common.GetFunctionName()) + sqls := [][]string{ + { + "SELECT * FROM tbl WHERE col = col = 'abc'", + "UPDATE tbl set col = 1 WHERE col = col = 'abc'", + "DELETE FROM tbl WHERE col = col = 'abc'", + }, + { + "SELECT * FROM tbl WHERE col = 'abc'", + }, + } + + for _, sql := range sqls[0] { + q, err := NewQuery4Audit(sql) + if err == nil { + rule := q.RuleMultiCompare() + if rule.Item != "RES.009" { + t.Error("Rule not match:", rule.Item, "Expect : RES.009, SQL: ", sql) + } + } else { + t.Error("sqlparser.Parse Error:", err) + } + } + + for _, sql := range sqls[1] { + q, err := NewQuery4Audit(sql) + if err == nil { + rule := q.RuleMultiCompare() + if rule.Item != "OK" { + t.Error("Rule not match:", rule.Item, "Expect : OK, SQL: ", sql) + } + } else { + t.Error("sqlparser.Parse Error:", err) + } + } + common.Log.Debug("Exiting function: %s", common.GetFunctionName()) +} + // STA.001 func TestRuleStandardINEQ(t *testing.T) { common.Log.Debug("Entering function: %s", common.GetFunctionName()) diff --git a/advisor/rules.go b/advisor/rules.go index 943c92b584dcb5088ab2dbcaaa12ef16bfa1b5ba..ff56c85ec305223f9f05faacd6a5aee209a4e103 100644 --- a/advisor/rules.go +++ b/advisor/rules.go @@ -965,6 +965,14 @@ func init() { Case: "LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;", Func: (*Query4Audit).RuleLoadFile, }, + "RES.009": { + Item: "RES.009", + Severity: "L2", + Summary: "不建议使用连续判断", + Content: "类似这样的 SELECT * FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。", + Case: "SELECT * FROM tbl WHERE col = col = 'abc'", + Func: (*Query4Audit).RuleMultiCompare, + }, "SEC.001": { Item: "SEC.001", Severity: "L0", diff --git a/advisor/testdata/TestListHeuristicRules.golden b/advisor/testdata/TestListHeuristicRules.golden index 35ed6c4b271452baa909171dd3c101bcbb079c65..1e135d7e686f725269a92573265cf263ed54b6e5 100644 --- a/advisor/testdata/TestListHeuristicRules.golden +++ b/advisor/testdata/TestListHeuristicRules.golden @@ -1022,6 +1022,16 @@ select * from tbl where 1 = 1; ```sql LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table; ``` +## 不建议使用连续判断 + +* **Item**:RES.009 +* **Severity**:L2 +* **Content**:类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 +* **Case**: + +```sql +SELECT * FROM tbl WHERE col = col = 'abc' +``` ## 请谨慎使用TRUNCATE操作 * **Item**:SEC.001 diff --git a/advisor/testdata/TestMergeConflictHeuristicRules.golden b/advisor/testdata/TestMergeConflictHeuristicRules.golden index 6821b63aa906d3c1b0ff178b25be7b71aaf9a30a..c17fc2c98036cdf7919df5e20b43530c2523d1b4 100644 --- a/advisor/testdata/TestMergeConflictHeuristicRules.golden +++ b/advisor/testdata/TestMergeConflictHeuristicRules.golden @@ -97,6 +97,7 @@ advisor.Rule{Item:"RES.005", Severity:"L4", Summary:"UPDATE 语句可能存在 advisor.Rule{Item:"RES.006", Severity:"L4", Summary:"永远不真的比较条件", Content:"查询条件永远非真,如果该条件出现在 where 中可能导致查询无匹配到的结果。", Case:"select * from tbl where 1 != 1;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"RES.007", Severity:"L4", Summary:"永远为真的比较条件", Content:"查询条件永远为真,可能导致 WHERE 条件失效进行全表查询。", Case:"select * from tbl where 1 = 1;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"RES.008", Severity:"L2", Summary:"不建议使用LOAD DATA/SELECT ... INTO OUTFILE", Content:"SELECT INTO OUTFILE 需要授予 FILE 权限,这通过会引入安全问题。LOAD DATA 虽然可以提高数据导入速度,但同时也可能导致从库同步延迟过大。", Case:"LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"RES.009", Severity:"L2", Summary:"不建议使用连续判断", Content:"类似这样的 SELECT * FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。", Case:"SELECT * FROM tbl WHERE col = col = 'abc'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"SEC.001", Severity:"L0", Summary:"请谨慎使用TRUNCATE操作", Content:"一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl_name;语句。但TRUNCATE操作也并非是毫无代价的,TRUNCATE TABLE无法返回被删除的准确行数,如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE 操作还会重置 AUTO_INCREMENT,如果不想重置该值建议使用 DELETE FROM tbl_name WHERE 1;替代。TRUNCATE 操作会对数据字典添加源数据锁(MDL),当一次需要 TRUNCATE 很多表时会影响整个实例的所有请求,因此如果要 TRUNCATE 多个表建议用 DROP+CREATE 的方式以减少锁时长。", Case:"TRUNCATE TABLE tbl_name", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"SEC.002", Severity:"L0", Summary:"不使用明文存储密码", Content:"使用明文存储密码或者使用明文在网络上传递密码都是不安全的。如果攻击者能够截获您用来插入密码的SQL语句,他们就能直接读到密码。另外,将用户输入的字符串以明文的形式插入到纯SQL语句中,也会让攻击者发现它。如果您能够读取密码,黑客也可以。解决方案是使用单向哈希函数对原始密码进行加密编码。哈希是指将输入字符串转化成另一个新的、不可识别的字符串的函数。对密码加密表达式加点随机串来防御“字典攻击”。不要将明文密码输入到SQL查询语句中。在应用程序代码中计算哈希串,只在SQL查询中使用哈希串。", Case:"create table test(id int,name varchar(20) not null,password varchar(200)not null)", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"SEC.003", Severity:"L0", Summary:"使用DELETE/DROP/TRUNCATE等操作时注意备份", Content:"在执行高危操作之前对数据进行备份是十分有必要的。", Case:"delete from table where col = 'condition'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} diff --git a/doc/heuristic.md b/doc/heuristic.md index 35ed6c4b271452baa909171dd3c101bcbb079c65..1e135d7e686f725269a92573265cf263ed54b6e5 100644 --- a/doc/heuristic.md +++ b/doc/heuristic.md @@ -1022,6 +1022,16 @@ select * from tbl where 1 = 1; ```sql LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table; ``` +## 不建议使用连续判断 + +* **Item**:RES.009 +* **Severity**:L2 +* **Content**:类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 +* **Case**: + +```sql +SELECT * FROM tbl WHERE col = col = 'abc' +``` ## 请谨慎使用TRUNCATE操作 * **Item**:SEC.001