github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sink/codec/common/helper.go (about) 1 // Copyright 2023 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 common 15 16 import ( 17 "context" 18 "database/sql" 19 "fmt" 20 "math" 21 22 "github.com/go-sql-driver/mysql" 23 "github.com/pingcap/log" 24 "github.com/pingcap/tiflow/pkg/errors" 25 "go.uber.org/zap" 26 ) 27 28 // ColumnsHolder read columns from sql.Rows 29 type ColumnsHolder struct { 30 Values []interface{} 31 ValuePointers []interface{} 32 Types []*sql.ColumnType 33 } 34 35 func newColumnHolder(rows *sql.Rows) (*ColumnsHolder, error) { 36 columnTypes, err := rows.ColumnTypes() 37 if err != nil { 38 return nil, errors.Trace(err) 39 } 40 41 values := make([]interface{}, len(columnTypes)) 42 valuePointers := make([]interface{}, len(columnTypes)) 43 for i := range values { 44 valuePointers[i] = &values[i] 45 } 46 47 return &ColumnsHolder{ 48 Values: values, 49 ValuePointers: valuePointers, 50 Types: columnTypes, 51 }, nil 52 } 53 54 // Length return the column count 55 func (h *ColumnsHolder) Length() int { 56 return len(h.Values) 57 } 58 59 // MustQueryTimezone query the timezone from the upstream database 60 func MustQueryTimezone(ctx context.Context, db *sql.DB) string { 61 conn, err := db.Conn(ctx) 62 if err != nil { 63 log.Panic("establish connection to the upstream tidb failed", zap.Error(err)) 64 } 65 defer conn.Close() 66 67 var timezone string 68 query := "SELECT @@global.time_zone" 69 err = conn.QueryRowContext(ctx, query).Scan(&timezone) 70 if err != nil { 71 log.Panic("query timezone failed", zap.Error(err)) 72 } 73 74 log.Info("query global timezone from the upstream tidb", 75 zap.Any("timezone", timezone)) 76 return timezone 77 } 78 79 // MustSnapshotQuery query the db by the snapshot read with the given commitTs 80 func MustSnapshotQuery( 81 ctx context.Context, db *sql.DB, commitTs uint64, schema, table string, conditions map[string]interface{}, 82 ) *ColumnsHolder { 83 conn, err := db.Conn(ctx) 84 if err != nil { 85 log.Panic("establish connection to the upstream tidb failed", 86 zap.String("schema", schema), zap.String("table", table), 87 zap.Uint64("commitTs", commitTs), zap.Error(err)) 88 } 89 defer conn.Close() 90 91 // 1. set snapshot read 92 query := fmt.Sprintf("set @@tidb_snapshot=%d", commitTs) 93 _, err = conn.ExecContext(ctx, query) 94 if err != nil { 95 mysqlErr, ok := errors.Cause(err).(*mysql.MySQLError) 96 if ok { 97 // Error 8055 (HY000): snapshot is older than GC safe point 98 if mysqlErr.Number == 8055 { 99 log.Error("set snapshot read failed, since snapshot is older than GC safe point") 100 } 101 } 102 103 log.Panic("set snapshot read failed", 104 zap.String("query", query), 105 zap.String("schema", schema), zap.String("table", table), 106 zap.Uint64("commitTs", commitTs), zap.Error(err)) 107 } 108 109 // 2. query the whole row 110 query = fmt.Sprintf("select * from %s.%s where ", schema, table) 111 var whereClause string 112 for name, value := range conditions { 113 if whereClause != "" { 114 whereClause += " and " 115 } 116 whereClause += fmt.Sprintf("%s = %v", name, value) 117 } 118 query += whereClause 119 120 rows, err := conn.QueryContext(ctx, query) 121 if err != nil { 122 log.Panic("query row failed", 123 zap.String("query", query), 124 zap.String("schema", schema), zap.String("table", table), 125 zap.Uint64("commitTs", commitTs), zap.Error(err)) 126 } 127 defer rows.Close() 128 129 holder, err := newColumnHolder(rows) 130 if err != nil { 131 log.Panic("obtain the columns holder failed", 132 zap.String("query", query), 133 zap.String("schema", schema), zap.String("table", table), 134 zap.Uint64("commitTs", commitTs), zap.Error(err)) 135 } 136 for rows.Next() { 137 err = rows.Scan(holder.ValuePointers...) 138 if err != nil { 139 log.Panic("scan row failed", 140 zap.String("query", query), 141 zap.String("schema", schema), zap.String("table", table), 142 zap.Uint64("commitTs", commitTs), zap.Error(err)) 143 } 144 } 145 return holder 146 } 147 148 // MustBinaryLiteralToInt convert bytes into uint64, 149 // by follow https://github.com/pingcap/tidb/blob/e3417913f58cdd5a136259b902bf177eaf3aa637/types/binary_literal.go#L105 150 func MustBinaryLiteralToInt(bytes []byte) uint64 { 151 bytes = trimLeadingZeroBytes(bytes) 152 length := len(bytes) 153 154 if length > 8 { 155 log.Panic("invalid bit value found", zap.ByteString("value", bytes)) 156 return math.MaxUint64 157 } 158 159 if length == 0 { 160 return 0 161 } 162 163 // Note: the byte-order is BigEndian. 164 val := uint64(bytes[0]) 165 for i := 1; i < length; i++ { 166 val = (val << 8) | uint64(bytes[i]) 167 } 168 return val 169 } 170 171 func trimLeadingZeroBytes(bytes []byte) []byte { 172 if len(bytes) == 0 { 173 return bytes 174 } 175 pos, posMax := 0, len(bytes)-1 176 for ; pos < posMax; pos++ { 177 if bytes[pos] != 0 { 178 break 179 } 180 } 181 return bytes[pos:] 182 }