github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/election/storage_orm.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 election 15 16 import ( 17 "context" 18 "database/sql" 19 "database/sql/driver" 20 "encoding/json" 21 22 "github.com/pingcap/log" 23 ormUtil "github.com/pingcap/tiflow/engine/pkg/orm" 24 "github.com/pingcap/tiflow/pkg/errors" 25 "gorm.io/gorm" 26 "gorm.io/gorm/clause" 27 ) 28 29 const ( 30 recordRowID = 1 31 // leaderRowID is not used for node probing, it is only used to lock the leader row. 32 leaderRowID = 2 33 ) 34 35 // Value implements the driver.Valuer interface 36 func (r Record) Value() (driver.Value, error) { 37 return json.Marshal(r) 38 } 39 40 // Scan implements the sql.Scanner interface 41 func (r *Record) Scan(value interface{}) error { 42 b, ok := value.([]byte) 43 if !ok { 44 return errors.New("type assertion to []byte failed") 45 } 46 47 return json.Unmarshal(b, r) 48 } 49 50 // TableNameElection is the table name of election. 51 var TableNameElection = "election" 52 53 // DO mapped from table <election> 54 type DO struct { 55 ID uint32 `gorm:"column:id;type:int(10) unsigned;primaryKey" json:"id"` 56 LeaderID string `gorm:"column:leader_id;type:text;not null" json:"leader_id"` 57 Record *Record `gorm:"column:record;type:text" json:"record"` 58 Version uint64 `gorm:"column:version;type:bigint(20) unsigned;not null" json:"version"` 59 } 60 61 // TableName Election's table name 62 func (*DO) TableName() string { 63 return TableNameElection 64 } 65 66 // ORMStorage is a storage implementation based on SQL database. 67 type ORMStorage struct { 68 db *gorm.DB 69 tableName string 70 } 71 72 // NewORMStorageFromSQLDB creates a new ORMStorage from sql.DB. 73 func NewORMStorageFromSQLDB(backendDB *sql.DB, tableName string) (*ORMStorage, error) { 74 db, err := ormUtil.NewGormDB(backendDB, "mysql") 75 if err != nil { 76 return nil, err 77 } 78 return NewORMStorage(db, tableName) 79 } 80 81 // NewORMStorage creates a new ORMStorage. 82 func NewORMStorage(db *gorm.DB, tableName string) (*ORMStorage, error) { 83 TableNameElection = tableName 84 if err := db.AutoMigrate(&DO{}); err != nil { 85 return nil, errors.Trace(err) 86 } 87 88 return &ORMStorage{ 89 db: db, 90 tableName: tableName, 91 }, nil 92 } 93 94 // Get implements Storage.Get. 95 func (s *ORMStorage) Get(ctx context.Context) (*Record, error) { 96 var do DO 97 98 ret := s.db.WithContext(ctx).Where("id = ?", recordRowID).Limit(1).Find(&do) 99 if ret.Error != nil { 100 if ret.Error == gorm.ErrRecordNotFound { 101 return &Record{}, nil 102 } 103 return nil, errors.Trace(ret.Error) 104 } 105 if ret.RowsAffected == 0 { 106 return &Record{}, nil 107 } 108 109 do.Record.Version = int64(do.Version) 110 return do.Record, nil 111 } 112 113 // Update implements Storage.Update. 114 func (s *ORMStorage) Update(ctx context.Context, record *Record, isLeaderChanged bool) error { 115 if record.Version == 0 { 116 if !isLeaderChanged { 117 log.Panic("invalid operation") 118 } 119 return s.create(ctx, record) 120 } 121 return s.update(ctx, record, isLeaderChanged) 122 } 123 124 func (s *ORMStorage) update(ctx context.Context, record *Record, isLeaderChanged bool) error { 125 handleRet := func(ret *gorm.DB) error { 126 if ret.Error != nil { 127 return errors.Trace(ret.Error) 128 } 129 if ret.RowsAffected != 1 { 130 return errors.ErrElectionRecordConflict.GenWithStackByArgs() 131 } 132 return nil 133 } 134 return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { 135 // TODO: find more efficient way 136 ret := tx.Where("id = ? AND version = ?", recordRowID, record.Version). 137 Updates(DO{ 138 LeaderID: record.LeaderID, 139 Record: record, 140 Version: uint64(record.Version) + 1, 141 }) 142 if err := handleRet(ret); err != nil { 143 return errors.Trace(err) 144 } 145 146 if isLeaderChanged { 147 ret := tx.Where("id = ?", leaderRowID). 148 Updates(DO{ 149 LeaderID: record.LeaderID, 150 Record: nil, 151 Version: uint64(record.Version) + 1, 152 }) 153 return handleRet(ret) 154 } 155 return nil 156 }) 157 } 158 159 func (s *ORMStorage) create(ctx context.Context, record *Record) error { 160 rows := []*DO{ 161 { 162 ID: recordRowID, 163 LeaderID: record.LeaderID, 164 Record: record, 165 Version: uint64(record.Version) + 1, 166 }, 167 { 168 ID: leaderRowID, 169 LeaderID: record.LeaderID, 170 Record: nil, /* record is not saved in leader row */ 171 Version: uint64(record.Version) + 1, /* equals to recordRow */ 172 }, 173 } 174 return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { 175 ret := tx.WithContext(ctx).Create(rows) 176 if ret.Error != nil { 177 return errors.Trace(ret.Error) 178 } 179 180 if ret.RowsAffected == 0 { 181 return errors.ErrElectionRecordConflict.GenWithStackByArgs() 182 } else if ret.RowsAffected != int64(len(rows)) { 183 log.Panic("Transaction atomicity is broken when updating election record") 184 } 185 return nil 186 }) 187 } 188 189 // TxnWithLeaderLock execute a transaction with leader row locked. 190 func (s *ORMStorage) TxnWithLeaderLock(ctx context.Context, leaderID string, fc func(tx *gorm.DB) error) error { 191 return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { 192 var do DO 193 ret := tx.Select("leader_id").Where("id = ? and leader_id = ?", leaderRowID, leaderID). 194 Clauses(clause.Locking{ 195 Strength: "SHARE", 196 Table: clause.Table{Name: clause.CurrentTable}, 197 }).Limit(1).Find(&do) 198 if ret.Error != nil { 199 if ret.Error == gorm.ErrRecordNotFound { 200 return errors.ErrElectorNotLeader.GenWithStackByArgs(leaderID) 201 } 202 return errors.Trace(ret.Error) 203 } 204 if ret.RowsAffected != 1 { 205 return errors.ErrElectorNotLeader.GenWithStackByArgs(leaderID) 206 } 207 return fc(tx) 208 }) 209 }