github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/restore/db.go (about) 1 // Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0. 2 3 package restore 4 5 import ( 6 "context" 7 "fmt" 8 "sort" 9 10 "github.com/pingcap/br/pkg/metautil" 11 12 "github.com/pingcap/errors" 13 "github.com/pingcap/log" 14 "github.com/pingcap/parser/model" 15 "github.com/pingcap/tidb/kv" 16 "go.uber.org/zap" 17 18 "github.com/pingcap/br/pkg/glue" 19 "github.com/pingcap/br/pkg/utils" 20 ) 21 22 // DB is a TiDB instance, not thread-safe. 23 type DB struct { 24 se glue.Session 25 } 26 27 // NewDB returns a new DB. 28 func NewDB(g glue.Glue, store kv.Storage) (*DB, error) { 29 se, err := g.CreateSession(store) 30 if err != nil { 31 return nil, errors.Trace(err) 32 } 33 // The session may be nil in raw kv mode 34 if se == nil { 35 return nil, nil 36 } 37 // Set SQL mode to None for avoiding SQL compatibility problem 38 err = se.Execute(context.Background(), "set @@sql_mode=''") 39 if err != nil { 40 return nil, errors.Trace(err) 41 } 42 return &DB{ 43 se: se, 44 }, nil 45 } 46 47 // ExecDDL executes the query of a ddl job. 48 func (db *DB) ExecDDL(ctx context.Context, ddlJob *model.Job) error { 49 var err error 50 tableInfo := ddlJob.BinlogInfo.TableInfo 51 dbInfo := ddlJob.BinlogInfo.DBInfo 52 switch ddlJob.Type { 53 case model.ActionCreateSchema: 54 err = db.se.CreateDatabase(ctx, dbInfo) 55 if err != nil { 56 log.Error("create database failed", zap.Stringer("db", dbInfo.Name), zap.Error(err)) 57 } 58 return errors.Trace(err) 59 case model.ActionCreateTable: 60 err = db.se.CreateTable(ctx, model.NewCIStr(ddlJob.SchemaName), tableInfo) 61 if err != nil { 62 log.Error("create table failed", 63 zap.Stringer("db", dbInfo.Name), 64 zap.Stringer("table", tableInfo.Name), 65 zap.Error(err)) 66 } 67 return errors.Trace(err) 68 } 69 70 if tableInfo != nil { 71 switchDBSQL := fmt.Sprintf("use %s;", utils.EncloseName(ddlJob.SchemaName)) 72 err = db.se.Execute(ctx, switchDBSQL) 73 if err != nil { 74 log.Error("switch db failed", 75 zap.String("query", switchDBSQL), 76 zap.String("db", ddlJob.SchemaName), 77 zap.Error(err)) 78 return errors.Trace(err) 79 } 80 } 81 err = db.se.Execute(ctx, ddlJob.Query) 82 if err != nil { 83 log.Error("execute ddl query failed", 84 zap.String("query", ddlJob.Query), 85 zap.String("db", ddlJob.SchemaName), 86 zap.Int64("historySchemaVersion", ddlJob.BinlogInfo.SchemaVersion), 87 zap.Error(err)) 88 } 89 return errors.Trace(err) 90 } 91 92 // CreateDatabase executes a CREATE DATABASE SQL. 93 func (db *DB) CreateDatabase(ctx context.Context, schema *model.DBInfo) error { 94 err := db.se.CreateDatabase(ctx, schema) 95 if err != nil { 96 log.Error("create database failed", zap.Stringer("db", schema.Name), zap.Error(err)) 97 } 98 return errors.Trace(err) 99 } 100 101 // CreateTable executes a CREATE TABLE SQL. 102 func (db *DB) CreateTable(ctx context.Context, table *metautil.Table) error { 103 err := db.se.CreateTable(ctx, table.DB.Name, table.Info) 104 if err != nil { 105 log.Error("create table failed", 106 zap.Stringer("db", table.DB.Name), 107 zap.Stringer("table", table.Info.Name), 108 zap.Error(err)) 109 return errors.Trace(err) 110 } 111 112 var restoreMetaSQL string 113 if table.Info.IsSequence() { 114 setValFormat := fmt.Sprintf("do setval(%s.%s, %%d);", 115 utils.EncloseName(table.DB.Name.O), 116 utils.EncloseName(table.Info.Name.O)) 117 if table.Info.Sequence.Cycle { 118 increment := table.Info.Sequence.Increment 119 // TiDB sequence's behaviour is designed to keep the same pace 120 // among all nodes within the same cluster. so we need restore round. 121 // Here is a hack way to trigger sequence cycle round > 0 according to 122 // https://github.com/pingcap/br/pull/242#issuecomment-631307978 123 // TODO use sql to set cycle round 124 nextSeqSQL := fmt.Sprintf("do nextval(%s.%s);", 125 utils.EncloseName(table.DB.Name.O), 126 utils.EncloseName(table.Info.Name.O)) 127 var setValSQL string 128 if increment < 0 { 129 setValSQL = fmt.Sprintf(setValFormat, table.Info.Sequence.MinValue) 130 } else { 131 setValSQL = fmt.Sprintf(setValFormat, table.Info.Sequence.MaxValue) 132 } 133 err = db.se.Execute(ctx, setValSQL) 134 if err != nil { 135 log.Error("restore meta sql failed", 136 zap.String("query", setValSQL), 137 zap.Stringer("db", table.DB.Name), 138 zap.Stringer("table", table.Info.Name), 139 zap.Error(err)) 140 return errors.Trace(err) 141 } 142 143 // trigger cycle round > 0 144 err = db.se.Execute(ctx, nextSeqSQL) 145 if err != nil { 146 log.Error("restore meta sql failed", 147 zap.String("query", nextSeqSQL), 148 zap.Stringer("db", table.DB.Name), 149 zap.Stringer("table", table.Info.Name), 150 zap.Error(err)) 151 return errors.Trace(err) 152 } 153 } 154 restoreMetaSQL = fmt.Sprintf(setValFormat, table.Info.AutoIncID) 155 err = db.se.Execute(ctx, restoreMetaSQL) 156 } else { 157 var alterAutoIncIDFormat string 158 switch { 159 case table.Info.IsView(): 160 return nil 161 default: 162 alterAutoIncIDFormat = "alter table %s.%s auto_increment = %d;" 163 } 164 restoreMetaSQL = fmt.Sprintf( 165 alterAutoIncIDFormat, 166 utils.EncloseName(table.DB.Name.O), 167 utils.EncloseName(table.Info.Name.O), 168 table.Info.AutoIncID) 169 if utils.NeedAutoID(table.Info) { 170 err = db.se.Execute(ctx, restoreMetaSQL) 171 } 172 } 173 174 if err != nil { 175 log.Error("restore meta sql failed", 176 zap.String("query", restoreMetaSQL), 177 zap.Stringer("db", table.DB.Name), 178 zap.Stringer("table", table.Info.Name), 179 zap.Error(err)) 180 return errors.Trace(err) 181 } 182 if table.Info.PKIsHandle && table.Info.ContainsAutoRandomBits() { 183 // this table has auto random id, we need rebase it 184 185 // we can't merge two alter query, because 186 // it will cause Error: [ddl:8200]Unsupported multi schema change 187 alterAutoRandIDSQL := fmt.Sprintf( 188 "alter table %s.%s auto_random_base = %d", 189 utils.EncloseName(table.DB.Name.O), 190 utils.EncloseName(table.Info.Name.O), 191 table.Info.AutoRandID) 192 193 err = db.se.Execute(ctx, alterAutoRandIDSQL) 194 if err != nil { 195 log.Error("alter AutoRandID failed", 196 zap.String("query", alterAutoRandIDSQL), 197 zap.Stringer("db", table.DB.Name), 198 zap.Stringer("table", table.Info.Name), 199 zap.Error(err)) 200 } 201 } 202 203 return errors.Trace(err) 204 } 205 206 // Close closes the connection. 207 func (db *DB) Close() { 208 db.se.Close() 209 } 210 211 // FilterDDLJobs filters ddl jobs. 212 func FilterDDLJobs(allDDLJobs []*model.Job, tables []*metautil.Table) (ddlJobs []*model.Job) { 213 // Sort the ddl jobs by schema version in descending order. 214 sort.Slice(allDDLJobs, func(i, j int) bool { 215 return allDDLJobs[i].BinlogInfo.SchemaVersion > allDDLJobs[j].BinlogInfo.SchemaVersion 216 }) 217 dbs := getDatabases(tables) 218 for _, db := range dbs { 219 // These maps is for solving some corner case. 220 // e.g. let "t=2" indicates that the id of database "t" is 2, if the ddl execution sequence is: 221 // rename "a" to "b"(a=1) -> drop "b"(b=1) -> create "b"(b=2) -> rename "b" to "a"(a=2) 222 // Which we cannot find the "create" DDL by name and id directly. 223 // To cover †his case, we must find all names and ids the database/table ever had. 224 dbIDs := make(map[int64]bool) 225 dbIDs[db.ID] = true 226 dbNames := make(map[string]bool) 227 dbNames[db.Name.String()] = true 228 for _, job := range allDDLJobs { 229 if job.BinlogInfo.DBInfo != nil { 230 if dbIDs[job.SchemaID] || dbNames[job.BinlogInfo.DBInfo.Name.String()] { 231 ddlJobs = append(ddlJobs, job) 232 // The the jobs executed with the old id, like the step 2 in the example above. 233 dbIDs[job.SchemaID] = true 234 // For the jobs executed after rename, like the step 3 in the example above. 235 dbNames[job.BinlogInfo.DBInfo.Name.String()] = true 236 } 237 } 238 } 239 } 240 241 type namePair struct { 242 db string 243 table string 244 } 245 246 for _, table := range tables { 247 tableIDs := make(map[int64]bool) 248 tableIDs[table.Info.ID] = true 249 tableNames := make(map[namePair]bool) 250 name := namePair{table.DB.Name.String(), table.Info.Name.String()} 251 tableNames[name] = true 252 for _, job := range allDDLJobs { 253 if job.BinlogInfo.TableInfo != nil { 254 name := namePair{job.SchemaName, job.BinlogInfo.TableInfo.Name.String()} 255 if tableIDs[job.TableID] || tableNames[name] { 256 ddlJobs = append(ddlJobs, job) 257 tableIDs[job.TableID] = true 258 // For truncate table, the id may be changed 259 tableIDs[job.BinlogInfo.TableInfo.ID] = true 260 tableNames[name] = true 261 } 262 } 263 } 264 } 265 return ddlJobs 266 } 267 268 func getDatabases(tables []*metautil.Table) (dbs []*model.DBInfo) { 269 dbIDs := make(map[int64]bool) 270 for _, table := range tables { 271 if !dbIDs[table.DB.ID] { 272 dbs = append(dbs, table.DB) 273 dbIDs[table.DB.ID] = true 274 } 275 } 276 return 277 }