Skip to content
This repository was archived by the owner on Nov 24, 2023. It is now read-only.

Commit cd46eee

Browse files
authored
onlineddl: report an error when online ddl only matches only one regex (#2182) (#2185)
1 parent ea66a07 commit cd46eee

File tree

7 files changed

+200
-16
lines changed

7 files changed

+200
-16
lines changed

_utils/terror_gen/errors_release.txt

+1
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ ErrConfigGenTableRouter,[code=20045:class=config:scope=internal:level=high], "Me
177177
ErrConfigGenColumnMapping,[code=20046:class=config:scope=internal:level=high], "Message: generate column mapping error, Workaround: Please check the `column-mappings` config in task configuration file."
178178
ErrConfigInvalidChunkFileSize,[code=20047:class=config:scope=internal:level=high], "Message: invalid `chunk-filesize` %v, Workaround: Please check the `chunk-filesize` config in task configuration file."
179179
ErrConfigOnlineDDLInvalidRegex,[code=20048:class=config:scope=internal:level=high], "Message: config '%s' regex pattern '%s' invalid, reason: %s, Workaround: Please check if params is correctly in the configuration file."
180+
ErrConfigOnlineDDLMistakeRegex,[code=20049:class=config:scope=internal:level=high], "Message: online ddl sql '%s' invalid, table %s fail to match '%s' online ddl regex, Workaround: Please update your `shadow-table-rules` or `trash-table-rules` in the configuration file."
180181
ErrBinlogExtractPosition,[code=22001:class=binlog-op:scope=internal:level=high]
181182
ErrBinlogInvalidFilename,[code=22002:class=binlog-op:scope=internal:level=high], "Message: invalid binlog filename"
182183
ErrBinlogParsePosFromStr,[code=22003:class=binlog-op:scope=internal:level=high]

dm/config/subtask.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ const (
4242

4343
DefaultShadowTableRules = "^_(.+)_(?:new|gho)$"
4444
DefaultTrashTableRules = "^_(.+)_(?:ghc|del|old)$"
45+
46+
ShadowTableRules = "shadow-table-rules"
47+
TrashTableRules = "trash-table-rules"
4548
)
4649

4750
var defaultMaxIdleConns = 2
@@ -309,7 +312,7 @@ func (c *SubTaskConfig) Adjust(verifyDecryptPassword bool) error {
309312
if len(c.ShadowTableRules) == 0 {
310313
c.ShadowTableRules = []string{DefaultShadowTableRules}
311314
} else {
312-
shadowTableRule, err := adjustOnlineTableRules("shadow-table-rules", c.ShadowTableRules)
315+
shadowTableRule, err := adjustOnlineTableRules(ShadowTableRules, c.ShadowTableRules)
313316
if err != nil {
314317
return err
315318
}
@@ -319,7 +322,7 @@ func (c *SubTaskConfig) Adjust(verifyDecryptPassword bool) error {
319322
if len(c.TrashTableRules) == 0 {
320323
c.TrashTableRules = []string{DefaultTrashTableRules}
321324
} else {
322-
trashTableRule, err := adjustOnlineTableRules("trash-table-rules", c.TrashTableRules)
325+
trashTableRule, err := adjustOnlineTableRules(TrashTableRules, c.TrashTableRules)
323326
if err != nil {
324327
return err
325328
}

errors.toml

+6
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,12 @@ description = ""
10721072
workaround = "Please check if params is correctly in the configuration file."
10731073
tags = ["internal", "high"]
10741074

1075+
[error.DM-config-20049]
1076+
message = "online ddl sql '%s' invalid, table %s fail to match '%s' online ddl regex"
1077+
description = ""
1078+
workaround = "Please update your `shadow-table-rules` or `trash-table-rules` in the configuration file."
1079+
tags = ["internal", "high"]
1080+
10751081
[error.DM-binlog-op-22001]
10761082
message = ""
10771083
description = ""

pkg/terror/error_list.go

+3
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ const (
240240
codeConfigGenColumnMapping
241241
codeConfigInvalidChunkFileSize
242242
codeConfigOnlineDDLInvalidRegex
243+
codeConfigOnlineDDLMistakeRegex
243244
)
244245

245246
// Binlog operation error code list.
@@ -865,6 +866,8 @@ var (
865866
ErrConfigInvalidChunkFileSize = New(codeConfigInvalidChunkFileSize, ClassConfig, ScopeInternal, LevelHigh, "invalid `chunk-filesize` %v", "Please check the `chunk-filesize` config in task configuration file.")
866867
ErrConfigOnlineDDLInvalidRegex = New(codeConfigOnlineDDLInvalidRegex, ClassConfig, ScopeInternal, LevelHigh,
867868
"config '%s' regex pattern '%s' invalid, reason: %s", "Please check if params is correctly in the configuration file.")
869+
ErrConfigOnlineDDLMistakeRegex = New(codeConfigOnlineDDLMistakeRegex, ClassConfig, ScopeInternal, LevelHigh,
870+
"online ddl sql '%s' invalid, table %s fail to match '%s' online ddl regex", "Please update your `shadow-table-rules` or `trash-table-rules` in the configuration file.")
868871

869872
// Binlog operation error.
870873
ErrBinlogExtractPosition = New(codeBinlogExtractPosition, ClassBinlogOp, ScopeInternal, LevelHigh, "", "")

syncer/ddl.go

+6
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ func (s *Syncer) splitAndFilterDDL(
133133

134134
statements := make([]string, 0, len(sqls))
135135
tables = make(map[string]*filter.Table)
136+
137+
if s.onlineDDL != nil {
138+
if err = s.onlineDDL.CheckRegex(stmt, schema, s.SourceTableNamesFlavor); err != nil {
139+
return nil, nil, err
140+
}
141+
}
136142
for _, sql := range sqls {
137143
stmt2, err2 := p.ParseOneStmt(sql, "", "")
138144
if err2 != nil {

syncer/ddl_test.go

+85-9
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,24 @@ package syncer
1515

1616
import (
1717
"context"
18-
"errors"
1918
"fmt"
2019

21-
"github.com/pingcap/dm/dm/config"
22-
tcontext "github.com/pingcap/dm/pkg/context"
23-
"github.com/pingcap/dm/pkg/log"
24-
parserpkg "github.com/pingcap/dm/pkg/parser"
25-
"github.com/pingcap/dm/pkg/utils"
26-
onlineddl "github.com/pingcap/dm/syncer/online-ddl-tools"
27-
2820
"github.com/DATA-DOG/go-sqlmock"
2921
. "github.com/pingcap/check"
22+
"github.com/pingcap/errors"
3023
"github.com/pingcap/parser"
3124
"github.com/pingcap/parser/ast"
3225
"github.com/pingcap/tidb-tools/pkg/filter"
3326
router "github.com/pingcap/tidb-tools/pkg/table-router"
3427
"go.uber.org/zap"
28+
29+
"github.com/pingcap/dm/dm/config"
30+
tcontext "github.com/pingcap/dm/pkg/context"
31+
"github.com/pingcap/dm/pkg/log"
32+
parserpkg "github.com/pingcap/dm/pkg/parser"
33+
"github.com/pingcap/dm/pkg/terror"
34+
"github.com/pingcap/dm/pkg/utils"
35+
onlineddl "github.com/pingcap/dm/syncer/online-ddl-tools"
3536
)
3637

3738
func (s *testSyncerSuite) TestAnsiQuotes(c *C) {
@@ -451,7 +452,7 @@ func (s *testSyncerSuite) TestResolveOnlineDDL(c *C) {
451452
c.Assert(err, IsNil)
452453
c.Assert(sqls, HasLen, 0)
453454
c.Assert(tables, HasLen, 0)
454-
sql = fmt.Sprintf("RENAME TABLE `test`.`%s` TO `test`.`t1`", ca.ghostname)
455+
sql = fmt.Sprintf("RENAME TABLE `test`.`t1` TO `test`.`%s`, `test`.`%s` TO `test`.`t1`", ca.trashName, ca.ghostname)
455456
stmt, err = p.ParseOneStmt(sql, "", "")
456457
c.Assert(err, IsNil)
457458
sqls, tables, err = syncer.splitAndFilterDDL(ec, p, stmt, "test")
@@ -463,6 +464,77 @@ func (s *testSyncerSuite) TestResolveOnlineDDL(c *C) {
463464
}
464465
}
465466

467+
func (s *testSyncerSuite) TestMistakeOnlineDDLRegex(c *C) {
468+
cases := []struct {
469+
onlineType string
470+
trashName string
471+
ghostname string
472+
matchGho bool
473+
}{
474+
{
475+
config.GHOST,
476+
"_t1_del",
477+
"_t1_gho_invalid",
478+
false,
479+
},
480+
{
481+
config.GHOST,
482+
"_t1_del_invalid",
483+
"_t1_gho",
484+
true,
485+
},
486+
{
487+
config.PT,
488+
"_t1_old",
489+
"_t1_new_invalid",
490+
false,
491+
},
492+
{
493+
config.PT,
494+
"_t1_old_invalid",
495+
"_t1_new",
496+
true,
497+
},
498+
}
499+
tctx := tcontext.Background().WithLogger(log.With(zap.String("test", "TestMistakeOnlineDDLRegex")))
500+
p := parser.New()
501+
502+
ec := eventContext{tctx: tctx}
503+
for _, ca := range cases {
504+
plugin, err := onlineddl.NewRealOnlinePlugin(tctx, s.cfg)
505+
c.Assert(err, IsNil)
506+
syncer := NewSyncer(s.cfg, nil)
507+
syncer.onlineDDL = plugin
508+
c.Assert(plugin.Clear(tctx), IsNil)
509+
510+
// ghost table
511+
sql := fmt.Sprintf("ALTER TABLE `test`.`%s` ADD COLUMN `n` INT", ca.ghostname)
512+
stmt, err := p.ParseOneStmt(sql, "", "")
513+
c.Assert(err, IsNil)
514+
sqls, tables, err := syncer.splitAndFilterDDL(ec, p, stmt, "test")
515+
c.Assert(err, IsNil)
516+
c.Assert(tables, HasLen, 0)
517+
table := ca.ghostname
518+
matchRules := config.ShadowTableRules
519+
if ca.matchGho {
520+
c.Assert(sqls, HasLen, 0)
521+
table = ca.trashName
522+
matchRules = config.TrashTableRules
523+
} else {
524+
c.Assert(sqls, HasLen, 1)
525+
c.Assert(sqls[0], Equals, sql)
526+
}
527+
sql = fmt.Sprintf("RENAME TABLE `test`.`t1` TO `test`.`%s`, `test`.`%s` TO `test`.`t1`", ca.trashName, ca.ghostname)
528+
stmt, err = p.ParseOneStmt(sql, "", "")
529+
c.Assert(err, IsNil)
530+
sqls, tables, err = syncer.splitAndFilterDDL(ec, p, stmt, "test")
531+
c.Assert(terror.ErrConfigOnlineDDLMistakeRegex.Equal(err), IsTrue)
532+
c.Assert(sqls, HasLen, 0)
533+
c.Assert(tables, HasLen, 0)
534+
c.Assert(err, ErrorMatches, ".*"+sql+".*"+table+".*"+matchRules+".*")
535+
}
536+
}
537+
466538
func (s *testSyncerSuite) TestDropSchemaInSharding(c *C) {
467539
var (
468540
targetDB = "target_db"
@@ -526,6 +598,10 @@ func (m mockOnlinePlugin) CheckAndUpdate(tctx *tcontext.Context, schemas map[str
526598
return nil
527599
}
528600

601+
func (m mockOnlinePlugin) CheckRegex(stmt ast.StmtNode, schema string, flavor utils.LowerCaseTableNamesFlavor) error {
602+
return nil
603+
}
604+
529605
func (s *testSyncerSuite) TestClearOnlineDDL(c *C) {
530606
var (
531607
targetDB = "target_db"

syncer/online-ddl-tools/online_ddl.go

+94-5
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,18 @@ import (
1919
"regexp"
2020
"sync"
2121

22-
"github.com/pingcap/failpoint"
23-
2422
"github.com/pingcap/dm/dm/config"
2523
"github.com/pingcap/dm/pkg/conn"
2624
tcontext "github.com/pingcap/dm/pkg/context"
2725
"github.com/pingcap/dm/pkg/cputil"
2826
parserpkg "github.com/pingcap/dm/pkg/parser"
2927
"github.com/pingcap/dm/pkg/terror"
28+
"github.com/pingcap/dm/pkg/utils"
3029
"github.com/pingcap/dm/syncer/dbconn"
3130

31+
"github.com/pingcap/failpoint"
3232
"github.com/pingcap/parser/ast"
33+
"github.com/pingcap/parser/model"
3334
"github.com/pingcap/tidb-tools/pkg/dbutil"
3435
"github.com/pingcap/tidb-tools/pkg/filter"
3536
"go.uber.org/zap"
@@ -50,7 +51,7 @@ type OnlinePlugin interface {
5051
Apply(tctx *tcontext.Context, tables []*filter.Table, statement string, stmt ast.StmtNode) ([]string, string, string, error)
5152
// Finish would delete online ddl from memory and storage
5253
Finish(tctx *tcontext.Context, schema, table string) error
53-
// TableType returns ghhost/real table
54+
// TableType returns ghost/real table
5455
TableType(table string) TableType
5556
// RealName returns real table name that removed ghost suffix and handled by table router
5657
RealName(table string) string
@@ -63,6 +64,8 @@ type OnlinePlugin interface {
6364
Close()
6465
// CheckAndUpdate try to check and fix the schema/table case-sensitive issue
6566
CheckAndUpdate(tctx *tcontext.Context, schemas map[string]string, tables map[string]map[string]string) error
67+
// CheckRegex checks the regex of shadow/trash table rules and reports an error if a ddl event matches only either of the rules
68+
CheckRegex(stmt ast.StmtNode, schema string, flavor utils.LowerCaseTableNamesFlavor) error
6669
}
6770

6871
// TableType is type of table.
@@ -75,6 +78,12 @@ const (
7578
TrashTable TableType = "trash table" // means we should ignore these tables
7679
)
7780

81+
const (
82+
shadowTable int = iota
83+
trashTable
84+
allTable
85+
)
86+
7887
// GhostDDLInfo stores ghost information and ddls.
7988
type GhostDDLInfo struct {
8089
Schema string `json:"schema"`
@@ -391,14 +400,14 @@ func NewRealOnlinePlugin(tctx *tcontext.Context, cfg *config.SubTaskConfig) (Onl
391400
for _, sg := range cfg.ShadowTableRules {
392401
shadowReg, err := regexp.Compile(sg)
393402
if err != nil {
394-
return nil, terror.ErrConfigOnlineDDLInvalidRegex.Generate("shadow-table-rules", sg, "fail to compile: "+err.Error())
403+
return nil, terror.ErrConfigOnlineDDLInvalidRegex.Generate(config.ShadowTableRules, sg, "fail to compile: "+err.Error())
395404
}
396405
shadowRegs = append(shadowRegs, shadowReg)
397406
}
398407
for _, tg := range cfg.TrashTableRules {
399408
trashReg, err := regexp.Compile(tg)
400409
if err != nil {
401-
return nil, terror.ErrConfigOnlineDDLInvalidRegex.Generate("trash-table-rules", tg, "fail to compile: "+err.Error())
410+
return nil, terror.ErrConfigOnlineDDLInvalidRegex.Generate(config.TrashTableRules, tg, "fail to compile: "+err.Error())
402411
}
403412
trashRegs = append(trashRegs, trashReg)
404413
}
@@ -558,3 +567,83 @@ func (r *RealOnlinePlugin) ResetConn(tctx *tcontext.Context) error {
558567
func (r *RealOnlinePlugin) CheckAndUpdate(tctx *tcontext.Context, schemas map[string]string, tables map[string]map[string]string) error {
559568
return r.storage.CheckAndUpdate(tctx, schemas, tables, r.RealName)
560569
}
570+
571+
// CheckRegex checks the regex of shadow/trash table rules and reports an error if a ddl event matches only either of the rules.
572+
func (r *RealOnlinePlugin) CheckRegex(stmt ast.StmtNode, schema string, flavor utils.LowerCaseTableNamesFlavor) error {
573+
var (
574+
v *ast.RenameTableStmt
575+
ok bool
576+
)
577+
if v, ok = stmt.(*ast.RenameTableStmt); !ok {
578+
return nil
579+
}
580+
t2ts := v.TableToTables
581+
if len(t2ts) != 2 {
582+
return nil
583+
}
584+
onlineDDLMatched := allTable
585+
tableRecords := make([]*filter.Table, 2)
586+
schemaName := model.NewCIStr(schema) // fill schema name
587+
588+
// Online DDL sql example: RENAME TABLE `test`.`t1` TO `test`.`_t1_old`, `test`.`_t1_new` TO `test`.`t1`
589+
// We should parse two rename DDL from this DDL:
590+
// tables[0] tables[1]
591+
// DDL 0 real table ───► trash table
592+
// DDL 1 shadow table ───► real table
593+
// If we only have one of them, that means users may configure a wrong trash/shadow table regex
594+
for i, t2t := range t2ts {
595+
if t2t.OldTable.Schema.O == "" {
596+
t2t.OldTable.Schema = schemaName
597+
}
598+
if t2t.NewTable.Schema.O == "" {
599+
t2t.NewTable.Schema = schemaName
600+
}
601+
602+
v.TableToTables = []*ast.TableToTable{t2t}
603+
604+
if i == 0 {
605+
tableRecords[trashTable] = fetchTable(t2t.NewTable, flavor)
606+
if r.TableType(t2t.OldTable.Name.String()) == RealTable &&
607+
r.TableType(t2t.NewTable.Name.String()) == TrashTable {
608+
onlineDDLMatched = trashTable
609+
}
610+
} else {
611+
tableRecords[shadowTable] = fetchTable(t2t.OldTable, flavor)
612+
if r.TableType(t2t.OldTable.Name.String()) == GhostTable &&
613+
r.TableType(t2t.NewTable.Name.String()) == RealTable {
614+
// if no trash table is not matched before, we should record that shadow table is matched here
615+
// if shadow table is matched before, we just return all tables are matched and a nil error
616+
if onlineDDLMatched != trashTable {
617+
onlineDDLMatched = shadowTable
618+
} else {
619+
onlineDDLMatched = allTable
620+
}
621+
}
622+
}
623+
}
624+
if onlineDDLMatched != allTable {
625+
return terror.ErrConfigOnlineDDLMistakeRegex.Generate(stmt.Text(), tableRecords[onlineDDLMatched^1], unmatchedOnlineDDLRules(onlineDDLMatched))
626+
}
627+
return nil
628+
}
629+
630+
func unmatchedOnlineDDLRules(match int) string {
631+
switch match {
632+
case shadowTable:
633+
return config.TrashTableRules
634+
case trashTable:
635+
return config.ShadowTableRules
636+
default:
637+
return ""
638+
}
639+
}
640+
641+
func fetchTable(t *ast.TableName, flavor utils.LowerCaseTableNamesFlavor) *filter.Table {
642+
var tb *filter.Table
643+
if flavor == utils.LCTableNamesSensitive {
644+
tb = &filter.Table{Schema: t.Schema.O, Name: t.Name.O}
645+
} else {
646+
tb = &filter.Table{Schema: t.Schema.L, Name: t.Name.L}
647+
}
648+
return tb
649+
}

0 commit comments

Comments
 (0)