github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/pkg/cyclic/mark/mark.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 mark
    15  
    16  import (
    17  	"context"
    18  	"database/sql"
    19  	"fmt"
    20  	"strings"
    21  
    22  	"github.com/go-sql-driver/mysql"
    23  	"github.com/pingcap/errors"
    24  	"github.com/pingcap/log"
    25  	cerror "github.com/pingcap/ticdc/pkg/errors"
    26  	"github.com/pingcap/ticdc/pkg/quotes"
    27  	"github.com/pingcap/ticdc/pkg/security"
    28  	"go.uber.org/zap"
    29  )
    30  
    31  const (
    32  	// SchemaName is the name of schema where all mark tables are created
    33  	SchemaName string = "tidb_cdc"
    34  	tableName  string = "repl_mark"
    35  
    36  	// CyclicReplicaIDCol is the name of replica ID in mark tables
    37  	CyclicReplicaIDCol string = "replica_id"
    38  
    39  	// OptCyclicConfig is the key that adds to changefeed options
    40  	// automatically is cyclic replication is on.
    41  	OptCyclicConfig string = "_cyclic_relax_sql_mode"
    42  )
    43  
    44  // GetMarkTableName returns mark table name regards to the tableID
    45  func GetMarkTableName(sourceSchema, sourceTable string) (schema, table string) { // nolint:exported
    46  	// TODO(neil) better unquote or just crc32 the name.
    47  	table = strings.Join([]string{tableName, sourceSchema, sourceTable}, "_")
    48  	schema = SchemaName
    49  	return
    50  }
    51  
    52  // IsMarkTable tells whether the table is a mark table or not.
    53  func IsMarkTable(schema, table string) bool {
    54  	const quoteSchemaName = "`" + SchemaName + "`"
    55  	const quotetableName = "`" + tableName
    56  
    57  	if schema == SchemaName || schema == quoteSchemaName {
    58  		return true
    59  	}
    60  	if strings.HasPrefix(table, quotetableName) {
    61  		return true
    62  	}
    63  	return strings.HasPrefix(table, tableName)
    64  }
    65  
    66  // TableName is an interface gets schema and table name.
    67  // Note it is only used for avoiding import model.TableName.
    68  type TableName interface {
    69  	GetSchema() string
    70  	GetTable() string
    71  }
    72  
    73  // CreateMarkTables creates mark table regard to the table name.
    74  //
    75  // Note table name is only for avoid write hotspot there is *NO* guarantee
    76  // normal tables and mark tables are one:one map.
    77  func CreateMarkTables(ctx context.Context, upstreamDSN string, upstreamCred *security.Credential, tables ...TableName) error {
    78  	tlsCfg, err := upstreamCred.ToTLSConfig()
    79  	if err != nil {
    80  		return cerror.WrapError(cerror.ErrCreateMarkTableFailed,
    81  			errors.Annotate(err, "fail to open upstream TiDB connection"))
    82  	}
    83  	if tlsCfg != nil {
    84  		tlsName := "cli-marktable"
    85  		err = mysql.RegisterTLSConfig(tlsName, tlsCfg)
    86  		if err != nil {
    87  			return cerror.WrapError(cerror.ErrCreateMarkTableFailed,
    88  				errors.Annotate(err, "fail to open upstream TiDB connection"))
    89  		}
    90  		if strings.Contains(upstreamDSN, "?") && strings.Contains(upstreamDSN, "=") {
    91  			upstreamDSN += ("&tls=" + tlsName)
    92  		} else {
    93  			upstreamDSN += ("?tls=" + tlsName)
    94  		}
    95  	}
    96  	db, err := sql.Open("mysql", upstreamDSN)
    97  	if err != nil {
    98  		return cerror.WrapError(cerror.ErrCreateMarkTableFailed,
    99  			errors.Annotate(err, "Open upsteam database connection failed"))
   100  	}
   101  	err = db.PingContext(ctx)
   102  	if err != nil {
   103  		return cerror.WrapError(cerror.ErrCreateMarkTableFailed,
   104  			errors.Annotate(err, "fail to open upstream TiDB connection"))
   105  	}
   106  
   107  	userTableCount := 0
   108  	for _, name := range tables {
   109  		if IsMarkTable(name.GetSchema(), name.GetTable()) {
   110  			continue
   111  		}
   112  		userTableCount++
   113  		schema, table := GetMarkTableName(name.GetSchema(), name.GetTable())
   114  		_, err = db.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s;", schema))
   115  		if err != nil {
   116  			return cerror.WrapError(cerror.ErrCreateMarkTableFailed,
   117  				errors.Annotate(err, "fail to create mark database"))
   118  		}
   119  		_, err = db.ExecContext(ctx, fmt.Sprintf(
   120  			`CREATE TABLE IF NOT EXISTS %s
   121  			(
   122  				bucket INT NOT NULL,
   123  				%s BIGINT UNSIGNED NOT NULL,
   124  				val BIGINT DEFAULT 0,
   125  				start_timestamp BIGINT DEFAULT 0,
   126  				PRIMARY KEY (bucket, %s)
   127  			);`, quotes.QuoteSchema(schema, table), CyclicReplicaIDCol, CyclicReplicaIDCol))
   128  		if err != nil {
   129  			return cerror.WrapError(cerror.ErrCreateMarkTableFailed,
   130  				errors.Annotatef(err, "fail to create mark table %s", table))
   131  		}
   132  	}
   133  	log.Info("create upstream mark done", zap.Int("count", userTableCount))
   134  	return nil
   135  }