github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sink/codec/internal/column.go (about)

     1  // Copyright 2022 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 internal
    15  
    16  import (
    17  	"encoding/base64"
    18  	"encoding/json"
    19  	"strconv"
    20  
    21  	"github.com/pingcap/log"
    22  	"github.com/pingcap/tidb/pkg/parser/mysql"
    23  	"github.com/pingcap/tiflow/cdc/model"
    24  	"go.uber.org/zap"
    25  )
    26  
    27  // Column is a type only used in codec internally.
    28  type Column struct {
    29  	Type byte `json:"t"`
    30  	// Deprecated: please use Flag instead.
    31  	WhereHandle *bool                `json:"h,omitempty"`
    32  	Flag        model.ColumnFlagType `json:"f"`
    33  	Value       any                  `json:"v"`
    34  }
    35  
    36  // FromRowChangeColumn converts from a row changed column to a codec column.
    37  func (c *Column) FromRowChangeColumn(col *model.Column) {
    38  	c.Type = col.Type
    39  	c.Flag = col.Flag
    40  	if c.Flag.IsHandleKey() {
    41  		whereHandle := true
    42  		c.WhereHandle = &whereHandle
    43  	}
    44  	if col.Value == nil {
    45  		c.Value = nil
    46  		return
    47  	}
    48  	switch col.Type {
    49  	case mysql.TypeString, mysql.TypeVarString, mysql.TypeVarchar:
    50  		var str string
    51  		switch col.Value.(type) {
    52  		case []byte:
    53  			str = string(col.Value.([]byte))
    54  		case string:
    55  			str = col.Value.(string)
    56  		default:
    57  			log.Panic("invalid column value, please report a bug", zap.Any("col", col))
    58  		}
    59  		if c.Flag.IsBinary() {
    60  			str = strconv.Quote(str)
    61  			str = str[1 : len(str)-1]
    62  		}
    63  		c.Value = str
    64  	default:
    65  		c.Value = col.Value
    66  	}
    67  }
    68  
    69  // ToRowChangeColumn converts from a codec column to a row changed column.
    70  func (c *Column) ToRowChangeColumn(name string) *model.Column {
    71  	col := new(model.Column)
    72  	col.Type = c.Type
    73  	col.Flag = c.Flag
    74  	col.Name = name
    75  	col.Value = c.Value
    76  	if c.Value == nil {
    77  		return col
    78  	}
    79  	switch col.Type {
    80  	case mysql.TypeString, mysql.TypeVarString, mysql.TypeVarchar:
    81  		str := col.Value.(string)
    82  		var err error
    83  		if c.Flag.IsBinary() {
    84  			str, err = strconv.Unquote("\"" + str + "\"")
    85  			if err != nil {
    86  				log.Panic("invalid column value, please report a bug", zap.Any("col", c), zap.Error(err))
    87  			}
    88  		}
    89  		col.Value = []byte(str)
    90  	case mysql.TypeFloat:
    91  		col.Value = float32(col.Value.(float64))
    92  	case mysql.TypeYear:
    93  		col.Value = int64(col.Value.(uint64))
    94  	case mysql.TypeEnum, mysql.TypeSet:
    95  		val, err := col.Value.(json.Number).Int64()
    96  		if err != nil {
    97  			log.Panic("invalid column value for enum, please report a bug",
    98  				zap.Any("col", c), zap.Error(err))
    99  		}
   100  		col.Value = uint64(val)
   101  	default:
   102  		col.Value = c.Value
   103  	}
   104  	return col
   105  }
   106  
   107  // FormatColumn formats a codec column.
   108  func FormatColumn(c Column) Column {
   109  	switch c.Type {
   110  	case mysql.TypeTinyBlob, mysql.TypeMediumBlob,
   111  		mysql.TypeLongBlob, mysql.TypeBlob:
   112  		if s, ok := c.Value.(string); ok {
   113  			var err error
   114  			c.Value, err = base64.StdEncoding.DecodeString(s)
   115  			if err != nil {
   116  				log.Panic("invalid column value, please report a bug", zap.Any("col", c), zap.Error(err))
   117  			}
   118  		}
   119  	case mysql.TypeFloat, mysql.TypeDouble:
   120  		if s, ok := c.Value.(json.Number); ok {
   121  			f64, err := s.Float64()
   122  			if err != nil {
   123  				log.Panic("invalid column value, please report a bug", zap.Any("col", c), zap.Error(err))
   124  			}
   125  			c.Value = f64
   126  		}
   127  	case mysql.TypeTiny, mysql.TypeShort, mysql.TypeLong, mysql.TypeLonglong, mysql.TypeInt24, mysql.TypeYear:
   128  		if s, ok := c.Value.(json.Number); ok {
   129  			var err error
   130  			if c.Flag.IsUnsigned() {
   131  				c.Value, err = strconv.ParseUint(s.String(), 10, 64)
   132  			} else {
   133  				c.Value, err = strconv.ParseInt(s.String(), 10, 64)
   134  			}
   135  			if err != nil {
   136  				log.Panic("invalid column value, please report a bug", zap.Any("col", c), zap.Error(err))
   137  			}
   138  		} else if f, ok := c.Value.(float64); ok {
   139  			if c.Flag.IsUnsigned() {
   140  				c.Value = uint64(f)
   141  			} else {
   142  				c.Value = int64(f)
   143  			}
   144  		}
   145  	case mysql.TypeBit:
   146  		if s, ok := c.Value.(json.Number); ok {
   147  			intNum, err := s.Int64()
   148  			if err != nil {
   149  				log.Panic("invalid column value, please report a bug", zap.Any("col", c), zap.Error(err))
   150  			}
   151  			c.Value = uint64(intNum)
   152  		}
   153  	}
   154  	return c
   155  }