github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sqlmodel/causality.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 sqlmodel 15 16 import ( 17 "fmt" 18 "strconv" 19 "strings" 20 21 timodel "github.com/pingcap/tidb/pkg/parser/model" 22 "github.com/pingcap/tidb/pkg/parser/mysql" 23 "github.com/pingcap/tidb/pkg/sessionctx" 24 "github.com/pingcap/tidb/pkg/tablecodec" 25 "github.com/pingcap/tiflow/dm/pkg/log" 26 "github.com/pingcap/tiflow/dm/pkg/utils" 27 "go.uber.org/zap" 28 ) 29 30 // CausalityKeys returns all string representation of causality keys. If two row 31 // changes has the same causality keys, they must be replicated sequentially. 32 func (r *RowChange) CausalityKeys() []string { 33 r.lazyInitWhereHandle() 34 35 ret := make([]string, 0, 1) 36 if r.preValues != nil { 37 ret = append(ret, r.getCausalityString(r.preValues)...) 38 } 39 if r.postValues != nil { 40 ret = append(ret, r.getCausalityString(r.postValues)...) 41 } 42 return ret 43 } 44 45 func columnNeeds2LowerCase(col *timodel.ColumnInfo) bool { 46 switch col.GetType() { 47 case mysql.TypeVarchar, mysql.TypeString, mysql.TypeVarString, mysql.TypeTinyBlob, 48 mysql.TypeMediumBlob, mysql.TypeBlob, mysql.TypeLongBlob: 49 return collationNeeds2LowerCase(col.GetCollate()) 50 } 51 return false 52 } 53 54 func collationNeeds2LowerCase(collation string) bool { 55 return strings.HasSuffix(collation, "_ci") 56 } 57 58 func columnValue2String(value interface{}) string { 59 var data string 60 switch v := value.(type) { 61 case nil: 62 data = "null" 63 case bool: 64 if v { 65 data = "1" 66 } else { 67 data = "0" 68 } 69 case int: 70 data = strconv.FormatInt(int64(v), 10) 71 case int8: 72 data = strconv.FormatInt(int64(v), 10) 73 case int16: 74 data = strconv.FormatInt(int64(v), 10) 75 case int32: 76 data = strconv.FormatInt(int64(v), 10) 77 case int64: 78 data = strconv.FormatInt(v, 10) 79 case uint8: 80 data = strconv.FormatUint(uint64(v), 10) 81 case uint16: 82 data = strconv.FormatUint(uint64(v), 10) 83 case uint32: 84 data = strconv.FormatUint(uint64(v), 10) 85 case uint64: 86 data = strconv.FormatUint(v, 10) 87 case float32: 88 data = strconv.FormatFloat(float64(v), 'f', -1, 32) 89 case float64: 90 data = strconv.FormatFloat(v, 'f', -1, 64) 91 case string: 92 data = v 93 case []byte: 94 data = string(v) 95 default: 96 data = fmt.Sprintf("%v", v) 97 } 98 99 return data 100 } 101 102 func genKeyString( 103 table string, 104 columns []*timodel.ColumnInfo, 105 values []interface{}, 106 ) string { 107 var buf strings.Builder 108 for i, data := range values { 109 if data == nil { 110 log.L().Debug("ignore null value", 111 zap.String("column", columns[i].Name.O), 112 zap.String("table", table)) 113 continue // ignore `null` value. 114 } 115 // one column key looks like:`column_val.column_name.` 116 117 val := columnValue2String(data) 118 if columnNeeds2LowerCase(columns[i]) { 119 val = strings.ToLower(val) 120 } 121 buf.WriteString(val) 122 buf.WriteString(".") 123 buf.WriteString(columns[i].Name.L) 124 buf.WriteString(".") 125 } 126 if buf.Len() == 0 { 127 log.L().Debug("all value are nil, no key generated", 128 zap.String("table", table)) 129 return "" // all values are `null`. 130 } 131 buf.WriteString(table) 132 return buf.String() 133 } 134 135 // truncateIndexValues truncate prefix index from data. 136 func truncateIndexValues( 137 ctx sessionctx.Context, 138 ti *timodel.TableInfo, 139 indexColumns *timodel.IndexInfo, 140 tiColumns []*timodel.ColumnInfo, 141 data []interface{}, 142 ) []interface{} { 143 values := make([]interface{}, 0, len(indexColumns.Columns)) 144 datums, err := utils.AdjustBinaryProtocolForDatum(ctx, data, tiColumns) 145 if err != nil { 146 log.L().Warn("adjust binary protocol for datum error", zap.Error(err)) 147 return data 148 } 149 tablecodec.TruncateIndexValues(ti, indexColumns, datums) 150 for _, datum := range datums { 151 values = append(values, datum.GetValue()) 152 } 153 return values 154 } 155 156 func (r *RowChange) getCausalityString(values []interface{}) []string { 157 pkAndUks := r.whereHandle.UniqueIdxs 158 if len(pkAndUks) == 0 { 159 // the table has no PK/UK, all values of the row consists the causality key 160 return []string{genKeyString(r.sourceTable.String(), r.sourceTableInfo.Columns, values)} 161 } 162 163 ret := make([]string, 0, len(pkAndUks)) 164 165 for _, indexCols := range pkAndUks { 166 // TODO: should not support multi value index and generate the value 167 // TODO: also fix https://github.com/pingcap/tiflow/issues/3286#issuecomment-971264282 168 if indexCols.MVIndex { 169 continue 170 } 171 cols, vals := getColsAndValuesOfIdx(r.sourceTableInfo.Columns, indexCols, values) 172 // handle prefix index 173 truncVals := truncateIndexValues(r.tiSessionCtx, r.sourceTableInfo, indexCols, cols, vals) 174 key := genKeyString(r.sourceTable.String(), cols, truncVals) 175 if len(key) > 0 { // ignore `null` value. 176 ret = append(ret, key) 177 } else { 178 log.L().Debug("ignore empty key", zap.String("table", r.sourceTable.String())) 179 } 180 } 181 182 if len(ret) == 0 { 183 // the table has no PK/UK, or all UK are NULL. all values of the row 184 // consists the causality key 185 return []string{genKeyString(r.sourceTable.String(), r.sourceTableInfo.Columns, values)} 186 } 187 188 return ret 189 }