github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/syncer/handle_error.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 syncer
    15  
    16  import (
    17  	"context"
    18  	"encoding/json"
    19  	"fmt"
    20  
    21  	"github.com/go-mysql-org/go-mysql/replication"
    22  	"github.com/pingcap/tidb/pkg/parser"
    23  	"github.com/pingcap/tidb/pkg/parser/ast"
    24  	"github.com/pingcap/tiflow/dm/pb"
    25  	"github.com/pingcap/tiflow/dm/pkg/binlog"
    26  	parserpkg "github.com/pingcap/tiflow/dm/pkg/parser"
    27  	"github.com/pingcap/tiflow/dm/pkg/terror"
    28  )
    29  
    30  // HandleError handle error for syncer.
    31  func (s *Syncer) HandleError(ctx context.Context, req *pb.HandleWorkerErrorRequest) (string, error) {
    32  	pos := req.BinlogPos
    33  
    34  	if len(pos) == 0 {
    35  		startLocation, isQueryEvent := s.getErrLocation()
    36  		if startLocation == nil {
    37  			return "", fmt.Errorf("source '%s' has no error", s.cfg.SourceID)
    38  		}
    39  
    40  		if !isQueryEvent && req.Op != pb.ErrorOp_Inject {
    41  			return "", fmt.Errorf("only support to handle ddl error currently, see https://docs.pingcap.com/tidb-data-migration/stable/error-handling for other errors")
    42  		}
    43  		pos = startLocation.Position.String()
    44  	} else {
    45  		startLocation, err := binlog.VerifyBinlogPos(pos)
    46  		if err != nil {
    47  			return "", err
    48  		}
    49  		pos = startLocation.String()
    50  	}
    51  
    52  	var err error
    53  	// remove outdated operators when add operator
    54  	s.streamerController.RemoveOutdated(s.checkpoint.FlushedGlobalPoint().Position)
    55  	if err != nil {
    56  		return "", err
    57  	}
    58  
    59  	if req.Op == pb.ErrorOp_List {
    60  		commands := s.streamerController.ListEqualAndAfter(pos)
    61  		commandsJSON, err1 := json.Marshal(commands)
    62  		if err1 != nil {
    63  			return "", err1
    64  		}
    65  		return string(commandsJSON), err1
    66  	}
    67  	if req.Op == pb.ErrorOp_Revert {
    68  		return "", s.streamerController.Delete(pos)
    69  	}
    70  
    71  	events := make([]*replication.BinlogEvent, 0)
    72  
    73  	if req.Op == pb.ErrorOp_Replace || req.Op == pb.ErrorOp_Inject {
    74  		events, err = s.genEvents(ctx, req.Sqls)
    75  		if err != nil {
    76  			return "", err
    77  		}
    78  	}
    79  
    80  	req.BinlogPos = pos
    81  
    82  	return "", s.streamerController.Set(req, events)
    83  }
    84  
    85  func (s *Syncer) genEvents(ctx context.Context, sqls []string) ([]*replication.BinlogEvent, error) {
    86  	events := make([]*replication.BinlogEvent, 0)
    87  
    88  	parser2, err := s.fromDB.GetParser(ctx)
    89  	if err != nil {
    90  		s.tctx.L().Error("failed to get SQL mode specified parser from upstream, using default SQL mode instead")
    91  		parser2 = parser.New()
    92  	}
    93  
    94  	for _, sql := range sqls {
    95  		node, err := parser2.ParseOneStmt(sql, "", "")
    96  		if err != nil {
    97  			return nil, terror.Annotatef(terror.ErrSyncerUnitParseStmt.New(err.Error()), "sql %s", sql)
    98  		}
    99  
   100  		switch node.(type) {
   101  		case ast.DDLNode:
   102  			tables, err := parserpkg.FetchDDLTables("", node, s.SourceTableNamesFlavor)
   103  			if err != nil {
   104  				return nil, err
   105  			}
   106  
   107  			schema := tables[0].Schema
   108  			if len(schema) == 0 {
   109  				return nil, terror.ErrSyncerUnitInjectDDLWithoutSchema.Generate(sql)
   110  			}
   111  			events = append(events, genQueryEvent([]byte(schema), []byte(sql)))
   112  		default:
   113  			// TODO: support DML
   114  			return nil, terror.ErrSyncerEvent.New("only support replace or inject with DDL currently")
   115  		}
   116  	}
   117  	return events, nil
   118  }
   119  
   120  // genQueryEvent generate QueryEvent with empty EventSize and LogPos.
   121  func genQueryEvent(schema, query []byte) *replication.BinlogEvent {
   122  	header := &replication.EventHeader{
   123  		EventType: replication.QUERY_EVENT,
   124  	}
   125  	queryEvent := &replication.QueryEvent{
   126  		Schema: schema,
   127  		Query:  query,
   128  	}
   129  	e := &replication.BinlogEvent{
   130  		Header: header,
   131  		Event:  queryEvent,
   132  	}
   133  	return e
   134  }