-
Notifications
You must be signed in to change notification settings - Fork 188
loader, syncer: support create/drop view #1310
base: master
Are you sure you want to change the base?
Changes from 14 commits
3fafe7b
246e4bb
a6878f8
7412f64
ba1e0b2
8089a8e
e431dda
0823ba2
fbcd1fc
7d6fa37
89934cc
b7f7438
8a54a3d
0b14140
d168716
4d94b05
16897d4
ad14b9d
1fa1a7e
a9fdc88
5f55bf3
a91adcb
ff9d825
690b7e7
56ba826
1cd0dc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,9 @@ import ( | |
"sync/atomic" | ||
"time" | ||
|
||
"github.com/pingcap/parser" | ||
tmysql "github.com/pingcap/parser/mysql" | ||
|
||
"github.com/pingcap/dm/dm/config" | ||
"github.com/pingcap/dm/dm/pb" | ||
"github.com/pingcap/dm/dm/unit" | ||
|
@@ -36,6 +39,7 @@ import ( | |
"github.com/pingcap/dm/pkg/dumpling" | ||
fr "github.com/pingcap/dm/pkg/func-rollback" | ||
"github.com/pingcap/dm/pkg/log" | ||
parserpkg "github.com/pingcap/dm/pkg/parser" | ||
"github.com/pingcap/dm/pkg/terror" | ||
"github.com/pingcap/dm/pkg/utils" | ||
|
||
|
@@ -384,7 +388,9 @@ type Loader struct { | |
|
||
// db -> tables | ||
// table -> data files | ||
db2Tables map[string]Tables2DataFiles | ||
db2Tables map[string]Tables2DataFiles | ||
// db -> views | ||
db2Views map[string]map[string]struct{} | ||
tableInfos map[string]*tableInfo | ||
|
||
// for every worker goroutine, not for every data file | ||
|
@@ -420,6 +426,7 @@ func NewLoader(cfg *config.SubTaskConfig) *Loader { | |
loader := &Loader{ | ||
cfg: cfg, | ||
db2Tables: make(map[string]Tables2DataFiles), | ||
db2Views: make(map[string]map[string]struct{}), | ||
tableInfos: make(map[string]*tableInfo), | ||
workerWg: new(sync.WaitGroup), | ||
logger: log.With(zap.String("task", cfg.Name), zap.String("unit", "load")), | ||
|
@@ -945,6 +952,44 @@ func (l *Loader) prepareTableFiles(files map[string]struct{}) error { | |
return nil | ||
} | ||
|
||
func (l *Loader) prepareViewFiles(files map[string]struct{}) error { | ||
for file := range files { | ||
if !strings.HasSuffix(file, "-schema-view.sql") { | ||
continue | ||
} | ||
|
||
idx := strings.LastIndex(file, "-schema-view.sql") | ||
name := file[:idx] | ||
fields := strings.Split(name, ".") | ||
if len(fields) != 2 { | ||
l.logger.Warn("invalid view file", zap.String("file", file)) | ||
continue | ||
} | ||
|
||
db, table := fields[0], fields[1] | ||
if l.skipSchemaAndTable(&filter.Table{Schema: db, Name: table}) { | ||
l.logger.Warn("ignore view file", zap.String("view file", file)) | ||
continue | ||
} | ||
// because there's a table file for view file, we skip this check | ||
tables := l.db2Tables[db] | ||
if _, ok := tables[table]; !ok { | ||
return terror.ErrLoadUnitNoTableFileForView.Generate(file) | ||
} | ||
|
||
views, ok := l.db2Views[db] | ||
if !ok { | ||
l.db2Views[db] = map[string]struct{}{} | ||
views = l.db2Views[db] | ||
} | ||
views[table] = struct{}{} | ||
|
||
l.totalFileCount.Add(1) // for view | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (l *Loader) prepareDataFiles(files map[string]struct{}) error { | ||
var dataFilesNumber float64 | ||
|
||
|
@@ -1045,6 +1090,11 @@ func (l *Loader) prepare() error { | |
return err | ||
} | ||
|
||
// Sql file for create view | ||
if err := l.prepareViewFiles(files); err != nil { | ||
return err | ||
} | ||
|
||
// Sql file for restore data | ||
return l.prepareDataFiles(files) | ||
} | ||
|
@@ -1083,6 +1133,103 @@ func (l *Loader) restoreTable(ctx context.Context, conn *DBConn, sqlFile, schema | |
return nil | ||
} | ||
|
||
// restoreView drops dummy table and create view | ||
func (l *Loader) restoreView(ctx context.Context, conn *DBConn, sqlFile, schema, view string) error { | ||
// dumpling will generate such a viewFile | ||
// /*!40101 SET NAMES binary*/; | ||
// DROP TABLE IF EXISTS `v2`; | ||
// DROP VIEW IF EXISTS `v2`; | ||
// SET @PREV_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; | ||
// SET @PREV_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS; | ||
// SET @PREV_COLLATION_CONNECTION=@@COLLATION_CONNECTION; | ||
// SET character_set_client = utf8; | ||
// SET character_set_results = utf8; | ||
// SET collation_connection = utf8_general_ci; | ||
// CREATE ALGORITHM=UNDEFINED DEFINER="root"@"localhost" SQL SECURITY DEFINER VIEW "all_mode"."v2" AS select "all_mode"."t2"."id" AS "id" from "all_mode"."t2"; | ||
// SET character_set_client = @PREV_CHARACTER_SET_CLIENT; | ||
// SET character_set_results = @PREV_CHARACTER_SET_RESULTS; | ||
// SET collation_connection = @PREV_COLLATION_CONNECTION; | ||
f, err := os.Open(sqlFile) | ||
if err != nil { | ||
return terror.ErrLoadUnitReadSchemaFile.Delegate(err) | ||
} | ||
defer f.Close() | ||
|
||
tctx := tcontext.NewContext(ctx, l.logger) | ||
|
||
var sqls []string | ||
dstSchema, dstView := fetchMatchedLiteral(tctx, l.tableRouter, schema, view) | ||
sqls = append(sqls, fmt.Sprintf("USE `%s`;", unescapePercent(dstSchema, l.logger))) | ||
sqlMode, err := tmysql.GetSQLMode(l.cfg.SQLMode) | ||
if err != nil { | ||
// should not happened | ||
return terror.ErrGetSQLModeFromStr.Generate(l.cfg.SQLMode) | ||
} | ||
|
||
data := make([]byte, 0, 1024*1024) | ||
br := bufio.NewReader(f) | ||
for { | ||
line, err := br.ReadString('\n') | ||
if err == io.EOF { | ||
break | ||
} | ||
|
||
realLine := strings.TrimSpace(line[:len(line)-1]) | ||
if len(realLine) == 0 { | ||
continue | ||
} | ||
|
||
data = append(data, []byte(realLine)...) | ||
if data[len(data)-1] == ';' { | ||
query := string(data) | ||
data = data[0:0] | ||
if strings.HasPrefix(query, "/*") && strings.HasSuffix(query, "*/;") { | ||
continue | ||
} | ||
|
||
if strings.HasPrefix(query, "DROP") { | ||
query = renameShardingTable(query, view, dstView, false) | ||
} else if strings.HasPrefix(query, "CREATE") { | ||
// create view statement could be complicated because it has a select | ||
p := parser.New() | ||
p.SetSQLMode(sqlMode) | ||
stmt, err := p.ParseOneStmt(query, "", "") | ||
if err != nil { | ||
return terror.ErrLoadUnitParseStatement.Generate(query) | ||
} | ||
|
||
tableNames, err := parserpkg.FetchDDLTableNames(schema, stmt) | ||
if err != nil { | ||
return terror.WithScope(err, terror.ScopeInternal) | ||
} | ||
targetTableNames := make([]*filter.Table, 0, len(tableNames)) | ||
for i := range tableNames { | ||
dstSchema, dstTable := fetchMatchedLiteral(tctx, l.tableRouter, tableNames[i].Schema, tableNames[i].Name) | ||
tableName := &filter.Table{ | ||
Schema: dstSchema, | ||
Name: dstTable, | ||
} | ||
targetTableNames = append(targetTableNames, tableName) | ||
} | ||
query, err = parserpkg.RenameDDLTable(stmt, targetTableNames) | ||
if err != nil { | ||
return terror.WithScope(err, terror.ScopeInternal) | ||
} | ||
} | ||
|
||
l.logger.Debug("view create statement", zap.String("sql", query)) | ||
|
||
sqls = append(sqls, query) | ||
} | ||
} | ||
err = conn.executeSQL(tctx, sqls) | ||
if err != nil { | ||
return terror.WithScope(err, terror.ScopeDownstream) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// restoreStruture creates schema or table | ||
func (l *Loader) restoreStructure(ctx context.Context, conn *DBConn, sqlFile string, schema string, table string) error { | ||
f, err := os.Open(sqlFile) | ||
|
@@ -1204,6 +1351,7 @@ func (l *Loader) restoreData(ctx context.Context) error { | |
|
||
for _, db := range dbs { | ||
tables := l.db2Tables[db] | ||
views := l.db2Views[db] | ||
|
||
// create db | ||
dbFile := fmt.Sprintf("%s/%s-schema-create.sql", l.cfg.Dir, db) | ||
|
@@ -1272,8 +1420,18 @@ func (l *Loader) restoreData(ctx context.Context) error { | |
dispatchMap[fmt.Sprintf("%s_%s_%s", db, table, file)] = j | ||
} | ||
} | ||
|
||
for view := range views { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to create the view in a different database with the table? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess user may create that type of view. And dumpling will generate a "table file" for each view at view's database, I guess this logic is fine? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done in d168716 |
||
viewFile := fmt.Sprintf("%s/%s.%s-schema-view.sql", l.cfg.Dir, db, view) | ||
l.logger.Info("start to create view", zap.String("view file", viewFile)) | ||
err := l.restoreView(ctx, dbConn, viewFile, db, view) | ||
if err != nil { | ||
return err | ||
} | ||
l.logger.Info("finish to create view", zap.String("view file", viewFile)) | ||
} | ||
Comment on lines
+1418
to
+1426
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if some views needed tables in other databases are not created yet? I think we should create views when all tables in all databases have been created correctly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done in d168716 |
||
} | ||
l.logger.Info("finish to create tables", zap.Duration("cost time", time.Since(begin))) | ||
l.logger.Info("finish to create tables and views", zap.Duration("cost time", time.Since(begin))) | ||
|
||
// a simple and naive approach to dispatch files randomly based on the feature of golang map(range by random) | ||
for _, j := range dispatchMap { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sames that
loader
will ignore allSET
sqls here. Is this by design?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forgot 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh,
query
will be added in L1222. We just didn't rename themThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, yes. My fault. Maybe you can add a comment before L1191 🤣