github.com/pingcap/tidb-lightning@v5.0.0-rc.0.20210428090220-84b649866577+incompatible/lightning/glue/glue.go (about)

     1  // Copyright 2020 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package glue
    15  
    16  import (
    17  	"context"
    18  	"database/sql"
    19  	"errors"
    20  
    21  	"github.com/pingcap/parser"
    22  	"github.com/pingcap/parser/ast"
    23  	"github.com/pingcap/parser/model"
    24  	"github.com/pingcap/parser/mysql"
    25  	"github.com/pingcap/tidb-lightning/lightning/checkpoints"
    26  	"github.com/pingcap/tidb-lightning/lightning/common"
    27  	"github.com/pingcap/tidb-lightning/lightning/config"
    28  	"github.com/pingcap/tidb-lightning/lightning/log"
    29  	"github.com/pingcap/tidb/types"
    30  	"github.com/pingcap/tidb/util/sqlexec"
    31  )
    32  
    33  type Glue interface {
    34  	OwnsSQLExecutor() bool
    35  	GetSQLExecutor() SQLExecutor
    36  	GetDB() (*sql.DB, error)
    37  	GetParser() *parser.Parser
    38  	GetTables(context.Context, string) ([]*model.TableInfo, error)
    39  	GetSession(context.Context) (checkpoints.Session, error)
    40  	OpenCheckpointsDB(context.Context, *config.Config) (checkpoints.CheckpointsDB, error)
    41  	// Record is used to report some information (key, value) to host TiDB, including progress, stage currently
    42  	Record(string, uint64)
    43  }
    44  
    45  type SQLExecutor interface {
    46  	// ExecuteWithLog and ObtainStringWithLog should support concurrently call and can't assure different calls goes to
    47  	// same underlying connection
    48  	ExecuteWithLog(ctx context.Context, query string, purpose string, logger log.Logger) error
    49  	ObtainStringWithLog(ctx context.Context, query string, purpose string, logger log.Logger) (string, error)
    50  	QueryStringsWithLog(ctx context.Context, query string, purpose string, logger log.Logger) ([][]string, error)
    51  	Close()
    52  }
    53  
    54  // sqlConnSession implement checkpoints.Session used only for lighting itself
    55  type sqlConnSession struct {
    56  	checkpoints.Session
    57  	conn *sql.Conn
    58  }
    59  
    60  func (session *sqlConnSession) Close() {
    61  	session.conn.Close()
    62  }
    63  
    64  func (session *sqlConnSession) Execute(ctx context.Context, sql string) ([]sqlexec.RecordSet, error) {
    65  	_, err := session.conn.ExecContext(ctx, sql)
    66  	return nil, err
    67  }
    68  
    69  func (session *sqlConnSession) CommitTxn(context.Context) error {
    70  	return errors.New("sqlConnSession doesn't have a valid CommitTxn implementation")
    71  }
    72  
    73  func (session *sqlConnSession) RollbackTxn(context.Context) {}
    74  
    75  func (session *sqlConnSession) PrepareStmt(sql string) (stmtID uint32, paramCount int, fields []*ast.ResultField, err error) {
    76  	return 0, 0, nil, errors.New("sqlConnSession doesn't have a valid PrepareStmt implementation")
    77  }
    78  
    79  func (session *sqlConnSession) ExecutePreparedStmt(ctx context.Context, stmtID uint32, param []types.Datum) (sqlexec.RecordSet, error) {
    80  	return nil, errors.New("sqlConnSession doesn't have a valid ExecutePreparedStmt implementation")
    81  }
    82  
    83  func (session *sqlConnSession) DropPreparedStmt(stmtID uint32) error {
    84  	return errors.New("sqlConnSession doesn't have a valid DropPreparedStmt implementation")
    85  }
    86  
    87  type ExternalTiDBGlue struct {
    88  	db     *sql.DB
    89  	parser *parser.Parser
    90  }
    91  
    92  func NewExternalTiDBGlue(db *sql.DB, sqlMode mysql.SQLMode) *ExternalTiDBGlue {
    93  	p := parser.New()
    94  	p.SetSQLMode(sqlMode)
    95  
    96  	return &ExternalTiDBGlue{db: db, parser: p}
    97  }
    98  
    99  func (e *ExternalTiDBGlue) GetSQLExecutor() SQLExecutor {
   100  	return e
   101  }
   102  
   103  func (e *ExternalTiDBGlue) ExecuteWithLog(ctx context.Context, query string, purpose string, logger log.Logger) error {
   104  	sql := common.SQLWithRetry{
   105  		DB:     e.db,
   106  		Logger: logger,
   107  	}
   108  	return sql.Exec(ctx, purpose, query)
   109  }
   110  
   111  func (e *ExternalTiDBGlue) ObtainStringWithLog(ctx context.Context, query string, purpose string, logger log.Logger) (string, error) {
   112  	var s string
   113  	err := common.SQLWithRetry{
   114  		DB:     e.db,
   115  		Logger: logger,
   116  	}.QueryRow(ctx, purpose, query, &s)
   117  	return s, err
   118  }
   119  
   120  func (e *ExternalTiDBGlue) QueryStringsWithLog(ctx context.Context, query string, purpose string, logger log.Logger) (result [][]string, finalErr error) {
   121  	finalErr = common.SQLWithRetry{
   122  		DB:     e.db,
   123  		Logger: logger,
   124  	}.Transact(ctx, purpose, func(c context.Context, tx *sql.Tx) (txErr error) {
   125  		rows, err := tx.QueryContext(c, query)
   126  		if err != nil {
   127  			return err
   128  		}
   129  		defer rows.Close()
   130  
   131  		colNames, err := rows.Columns()
   132  		if err != nil {
   133  			return err
   134  		}
   135  		for rows.Next() {
   136  			row := make([]string, len(colNames))
   137  			refs := make([]interface{}, 0, len(row))
   138  			for i := range row {
   139  				refs = append(refs, &row[i])
   140  			}
   141  			if err := rows.Scan(refs...); err != nil {
   142  				return err
   143  			}
   144  			result = append(result, row)
   145  		}
   146  
   147  		return rows.Err()
   148  	})
   149  	return
   150  }
   151  
   152  func (e *ExternalTiDBGlue) GetDB() (*sql.DB, error) {
   153  	return e.db, nil
   154  }
   155  
   156  func (e *ExternalTiDBGlue) GetParser() *parser.Parser {
   157  	return e.parser
   158  }
   159  
   160  func (e ExternalTiDBGlue) GetTables(context.Context, string) ([]*model.TableInfo, error) {
   161  	return nil, errors.New("ExternalTiDBGlue doesn't have a valid GetTables function")
   162  }
   163  
   164  func (e *ExternalTiDBGlue) GetSession(ctx context.Context) (checkpoints.Session, error) {
   165  	conn, err := e.db.Conn(ctx)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	return &sqlConnSession{conn: conn}, nil
   170  }
   171  
   172  func (e *ExternalTiDBGlue) OpenCheckpointsDB(ctx context.Context, cfg *config.Config) (checkpoints.CheckpointsDB, error) {
   173  	return checkpoints.OpenCheckpointsDB(ctx, cfg)
   174  }
   175  
   176  func (e *ExternalTiDBGlue) OwnsSQLExecutor() bool {
   177  	return true
   178  }
   179  
   180  func (e *ExternalTiDBGlue) Close() {
   181  	e.db.Close()
   182  }
   183  
   184  func (e *ExternalTiDBGlue) Record(string, uint64) {
   185  }
   186  
   187  const (
   188  	RecordEstimatedChunk = "EstimatedChunk"
   189  	RecordFinishedChunk  = "FinishedChunk"
   190  )