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  }