github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/orm/util.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 orm 15 16 import ( 17 "context" 18 "database/sql" 19 "strings" 20 "time" 21 22 "github.com/glebarez/sqlite" 23 "github.com/pingcap/failpoint" 24 dmutils "github.com/pingcap/tiflow/dm/pkg/conn" 25 metaModel "github.com/pingcap/tiflow/engine/pkg/meta/model" 26 ormModel "github.com/pingcap/tiflow/engine/pkg/orm/model" 27 "github.com/pingcap/tiflow/pkg/errors" 28 "github.com/pingcap/tiflow/pkg/logutil" 29 "gorm.io/driver/mysql" 30 "gorm.io/gorm" 31 ) 32 33 var defaultSlowLogThreshold = 200 * time.Millisecond 34 35 // IsNotFoundError checks whether the error is ErrMetaEntryNotFound 36 // TODO: refine me, need wrap error for api 37 func IsNotFoundError(err error) bool { 38 if err == nil { 39 return false 40 } 41 return strings.Contains(err.Error(), "ErrMetaEntryNotFound") 42 } 43 44 // InitAllFrameworkModels will create all framework-related tables in SQL backend 45 // NOT thread-safe. 46 // TODO: What happen if we upgrade the definition of model when rolling update? 47 // TODO: need test: change column definition/add column/drop column? 48 func InitAllFrameworkModels(ctx context.Context, cc metaModel.ClientConn) error { 49 gormDB, err := genGormDBFromClientConn(cc) 50 if err != nil { 51 return err 52 } 53 54 failpoint.InjectContext(ctx, "initializedDelay", nil) 55 if err := gormDB.WithContext(ctx). 56 AutoMigrate(globalModels...); err != nil { 57 return errors.ErrMetaOpFail.Wrap(err) 58 } 59 60 return nil 61 } 62 63 // InitEpochModel creates the backend logic epoch table if not exists 64 // Only use for business meta currently 65 // NOT thread-safe 66 func InitEpochModel(ctx context.Context, cc metaModel.ClientConn) error { 67 gormDB, err := genGormDBFromClientConn(cc) 68 if err != nil { 69 return err 70 } 71 if err := gormDB.WithContext(ctx). 72 AutoMigrate(&ormModel.LogicEpoch{}); err != nil { 73 return errors.ErrMetaOpFail.Wrap(err) 74 } 75 76 return nil 77 } 78 79 func genGormDBFromClientConn(cc metaModel.ClientConn) (*gorm.DB, error) { 80 if cc == nil { 81 return nil, errors.ErrMetaParamsInvalid.GenWithStackByArgs("input client conn is nil") 82 } 83 84 conn, err := cc.GetConn() 85 if err != nil { 86 return nil, err 87 } 88 89 // check if a sql.DB 90 db, ok := conn.(*sql.DB) 91 if !ok { 92 return nil, errors.ErrMetaParamsInvalid.GenWithStackByArgs("client conn is not sql DB") 93 } 94 gormDB, err := NewGormDB(db, cc.StoreType()) 95 if err != nil { 96 return nil, err 97 } 98 99 return gormDB, nil 100 } 101 102 // NewGormDB news a gorm.DB 103 func NewGormDB(sqlDB *sql.DB, storeType metaModel.StoreType) (*gorm.DB, error) { 104 var dialector gorm.Dialector 105 if storeType == metaModel.StoreTypeMySQL { 106 dialector = mysql.New(mysql.Config{ 107 Conn: sqlDB, 108 SkipInitializeWithVersion: false, 109 }) 110 } else if storeType == metaModel.StoreTypeSQLite { 111 dialector = sqlite.Dialector{ 112 Conn: sqlDB, 113 } 114 } else { 115 return nil, errors.ErrMetaParamsInvalid.GenWithStack("SQL subtype is not supported yet:%s", storeType) 116 } 117 118 db, err := gorm.Open(dialector, &gorm.Config{ 119 SkipDefaultTransaction: true, 120 Logger: NewOrmLogger(logutil.WithComponent("gorm"), 121 WithSlowThreshold(defaultSlowLogThreshold), 122 WithIgnoreTraceRecordNotFoundErr()), 123 }) 124 if err != nil { 125 return nil, errors.ErrMetaNewClientFail.Wrap(err) 126 } 127 128 return db, nil 129 } 130 131 // IsDuplicateEntryError checks whether error contains DuplicateEntry(MySQL) error 132 // or UNIQUE constraint failed(SQLite) error underlying. 133 func IsDuplicateEntryError(err error) bool { 134 if err == nil { 135 return false 136 } 137 return dmutils.IsErrDuplicateEntry(err) || 138 strings.Contains(err.Error(), "UNIQUE constraint failed") 139 }