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

add new rules about ON UPDATE CURRENT_TIMESTAMP

上级 5b03689e
......@@ -1347,6 +1347,83 @@ func (q *Query4Audit) RuleMultiCompare() Rule {
return rule
}
// RuleCreateOnUpdate RES.010
func (q *Query4Audit) RuleCreateOnUpdate() Rule {
var rule = q.RuleOK()
switch q.Stmt.(type) {
case *sqlparser.DDL:
for _, tiStmt := range q.TiStmt {
switch node := tiStmt.(type) {
case *tidb.CreateTableStmt:
for _, col := range node.Cols {
if col.Tp == nil {
continue
}
for _, op := range col.Options {
if op.Tp == tidb.ColumnOptionOnUpdate {
rule = HeuristicRules["RES.010"]
return rule
}
}
}
case *tidb.AlterTableStmt:
for _, spec := range node.Specs {
switch spec.Tp {
case tidb.AlterTableAddColumns, tidb.AlterTableModifyColumn, tidb.AlterTableChangeColumn:
for _, col := range spec.NewColumns {
if col.Tp == nil {
continue
}
for _, op := range col.Options {
if op.Tp == tidb.ColumnOptionOnUpdate {
rule = HeuristicRules["RES.010"]
return rule
}
}
}
}
}
}
}
}
return rule
}
// RuleUpdateOnUpdate RES.011
func (idxAdv *IndexAdvisor) RuleUpdateOnUpdate() Rule {
rule := HeuristicRules["OK"]
// 未开启测试环境不进行检查
if common.Config.TestDSN.Disable {
return rule
}
err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
switch stmt := node.(type) {
case *sqlparser.Update:
for _, tbExpr := range stmt.TableExprs {
ddl, err := idxAdv.vEnv.ShowCreateTable(sqlparser.String(tbExpr))
if err != nil {
common.Log.Error("RuleMaxTextColsCount create statement got failed: %s", err.Error())
return false, err
}
if strings.Contains(ddl, "ON UPDATE") {
rule = HeuristicRules["RES.011"]
break
}
}
for _, setExpr := range stmt.Exprs {
tup := strings.Split(sqlparser.String(setExpr), " = ")
if len(tup) == 2 && tup[0] == tup[1] {
rule = HeuristicRules["OK"]
}
}
}
return true, nil
}, idxAdv.Ast)
common.LogIfError(err, "")
return rule
}
// RuleStandardINEQ STA.001
func (q *Query4Audit) RuleStandardINEQ() Rule {
var rule = q.RuleOK()
......
......@@ -1004,6 +1004,111 @@ func TestRuleMultiCompare(t *testing.T) {
common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}
// RES.010
func TestRuleCreateOnUpdate(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := [][]string{
{
`CREATE TABLE category (
category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(25) NOT NULL,
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (category_id)
)`,
},
{
`CREATE TABLE category (
category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(25) NOT NULL,
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (category_id)
)`,
},
}
for _, sql := range sqls[0] {
q, err := NewQuery4Audit(sql)
if err == nil {
rule := q.RuleCreateOnUpdate()
if rule.Item != "RES.010" {
t.Error("Rule not match:", rule.Item, "Expect : RES.010, SQL: ", sql)
}
} else {
t.Error("sqlparser.Parse Error:", err)
}
}
for _, sql := range sqls[1] {
q, err := NewQuery4Audit(sql)
if err == nil {
rule := q.RuleCreateOnUpdate()
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())
}
// RES.011
func TestRuleUpdateOnUpdate(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := [][]string{
{
`UPDATE category SET name='ActioN' WHERE category_id=1`,
},
{
`select * from film limit 1`,
"UPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1",
},
}
for _, sql := range sqls[0] {
vEnv.BuildVirtualEnv(rEnv, sql)
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
t.Error(syntaxErr)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.RuleUpdateOnUpdate()
if rule.Item != "RES.011" {
t.Error("Rule not match:", rule.Item, "Expect : RES.011, SQL:", sql)
}
}
}
for _, sql := range sqls[1] {
vEnv.BuildVirtualEnv(rEnv, sql)
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
t.Error(syntaxErr)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.RuleUpdateOnUpdate()
if rule.Item != "OK" {
t.Error("Rule not match:", rule.Item, "Expect : OK, SQL:", sql)
}
}
}
common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}
// STA.001
func TestRuleStandardINEQ(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
......
......@@ -429,7 +429,7 @@ func init() {
Summary: "不要 UPDATE 主键",
Content: `主键是数据表中记录的唯一标识符,不建议频繁更新主键列,这将影响元数据统计信息进而影响正常的查询。`,
Case: "update tbl set col=1",
Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给
Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给 RuleUpdatePrimaryKey
},
"COL.001": {
Item: "COL.001",
......@@ -689,7 +689,7 @@ func init() {
Summary: "不建议对等值查询列使用 GROUP BY",
Content: `GROUP BY 中的列在前面的 WHERE 条件中使用了等值查询,对这样的列进行 GROUP BY 意义不大。`,
Case: "select film_id, title from film where release_year='2006' group by release_year",
Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给
Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给 RuleGroupByConst
},
"JOI.001": {
Item: "JOI.001",
......@@ -989,6 +989,22 @@ func init() {
Case: "SELECT * FROM tbl WHERE col = col = 'abc'",
Func: (*Query4Audit).RuleMultiCompare,
},
"RES.010": {
Item: "RES.010",
Severity: "L2",
Summary: "建表语句中定义为 ON UPDATE CURRENT_TIMESTAMP 的字段不建议包含业务逻辑",
Content: "定义为 ON UPDATE CURRENT_TIMESTAMP 的字段在该表其他字段更新时会联动修改,如果包含业务逻辑用户可见会埋下隐患。后续如有批量修改数据却又不想修改该字段时会导致数据错误。",
Case: `CREATE TABLE category (category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(25) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (category_id)`,
Func: (*Query4Audit).RuleCreateOnUpdate,
},
"RES.011": {
Item: "RES.011",
Severity: "L2",
Summary: "更新请求操作的表包含 ON UPDATE CURRENT_TIMESTAMP 字段",
Content: "定义为 ON UPDATE CURRENT_TIMESTAMP 的字段在该表其他字段更新时会联动修改,请注意检查。如不想修改字段的更新时间可以使用如下方法:UPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1",
Case: "UPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1",
Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给 RuleUpdateOnUpdate
},
"SEC.001": {
Item: "SEC.001",
Severity: "L0",
......
......@@ -1052,6 +1052,26 @@ LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;
```sql
SELECT * FROM tbl WHERE col = col = 'abc'
```
## 建表语句中定义为 ON UPDATE CURRENT\_TIMESTAMP 的字段不建议包含业务逻辑
* **Item**:RES.010
* **Severity**:L2
* **Content**:定义为 ON UPDATE CURRENT\_TIMESTAMP 的字段在该表其他字段更新时会联动修改,如果包含业务逻辑用户可见会埋下隐患。后续如有批量修改数据却又不想修改该字段时会导致数据错误。
* **Case**:
```sql
CREATE TABLE category (category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(25) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (category_id)
```
## 更新请求操作的表包含 ON UPDATE CURRENT\_TIMESTAMP 字段
* **Item**:RES.011
* **Severity**:L2
* **Content**:定义为 ON UPDATE CURRENT\_TIMESTAMP 的字段在该表其他字段更新时会联动修改,请注意检查。如不想修改字段的更新时间可以使用如下方法:UPDATE category SET name='ActioN', last\_update=last\_update WHERE category\_id=1
* **Case**:
```sql
UPDATE category SET name='ActioN', last_update=last_update WHERE category_id=1
```
## 请谨慎使用TRUNCATE操作
* **Item**:SEC.001
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册