github.com/NpoolPlatform/chain-middleware@v0.0.0-20240228100535-eb1bcf896eb9/pkg/migrator/migrator.go (about) 1 //nolint 2 package migrator 3 4 import ( 5 "context" 6 "database/sql" 7 "fmt" 8 "strings" 9 "time" 10 11 servicename "github.com/NpoolPlatform/chain-middleware/pkg/servicename" 12 "github.com/NpoolPlatform/go-service-framework/pkg/config" 13 "github.com/NpoolPlatform/go-service-framework/pkg/logger" 14 constant "github.com/NpoolPlatform/go-service-framework/pkg/mysql/const" 15 redis2 "github.com/NpoolPlatform/go-service-framework/pkg/redis" 16 basetypes "github.com/NpoolPlatform/message/npool/basetypes/v1" 17 18 "github.com/google/uuid" 19 ) 20 21 const ( 22 keyUsername = "username" 23 keyPassword = "password" 24 keyDBName = "database_name" 25 maxOpen = 5 26 maxIdle = 2 27 MaxLife = 0 28 keyServiceID = "serviceid" 29 ) 30 31 func lockKey() string { 32 serviceID := config.GetStringValueWithNameSpace(servicename.ServiceDomain, keyServiceID) 33 return fmt.Sprintf("%v:%v", basetypes.Prefix_PrefixMigrate, serviceID) 34 } 35 36 func dsn(hostname string) (string, error) { 37 username := config.GetStringValueWithNameSpace(constant.MysqlServiceName, keyUsername) 38 password := config.GetStringValueWithNameSpace(constant.MysqlServiceName, keyPassword) 39 dbname := config.GetStringValueWithNameSpace(hostname, keyDBName) 40 41 svc, err := config.PeekService(constant.MysqlServiceName) 42 if err != nil { 43 logger.Sugar().Warnw("dsn", "error", err) 44 return "", err 45 } 46 47 return fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?parseTime=true&interpolateParams=true", 48 username, password, 49 svc.Address, 50 svc.Port, 51 dbname, 52 ), nil 53 } 54 55 func open(hostname string) (conn *sql.DB, err error) { 56 hdsn, err := dsn(hostname) 57 if err != nil { 58 return nil, err 59 } 60 61 logger.Sugar().Infow("open", "hdsn", hdsn) 62 63 conn, err = sql.Open("mysql", hdsn) 64 if err != nil { 65 return nil, err 66 } 67 68 // https://github.com/go-sql-driver/mysql 69 // See "Important settings" section. 70 71 conn.SetConnMaxLifetime(time.Minute * MaxLife) 72 conn.SetMaxOpenConns(maxOpen) 73 conn.SetMaxIdleConns(maxIdle) 74 75 return conn, nil 76 } 77 78 func tables(ctx context.Context, dbName string, tx *sql.DB) ([]string, error) { 79 tables := []string{} 80 rows, err := tx.QueryContext( 81 ctx, 82 fmt.Sprintf("select table_name from information_schema.tables where table_schema = '%v'", dbName), 83 ) 84 if err != nil { 85 return nil, err 86 } 87 for rows.Next() { 88 table := []byte{} 89 if err := rows.Scan(&table); err != nil { 90 return nil, err 91 } 92 tables = append(tables, string(table)) 93 } 94 logger.Sugar().Infow( 95 "tables", 96 "Tables", tables, 97 ) 98 return tables, nil 99 } 100 101 func existIDInt(ctx context.Context, dbName, table string, tx *sql.DB) (bool, bool, bool, error) { 102 _type := []byte{} 103 rc := 0 104 105 rows, err := tx.QueryContext( 106 ctx, 107 fmt.Sprintf("select column_type,1 from information_schema.columns where table_name='%v' and column_name='id' and table_schema='%v'", table, dbName), 108 ) 109 if err != nil { 110 return false, false, false, err 111 } 112 for rows.Next() { 113 if err := rows.Scan(&_type, &rc); err != nil { 114 return false, false, false, err 115 } 116 } 117 return rc == 1, strings.Contains(string(_type), "int"), strings.Contains(string(_type), "unsigned"), nil 118 } 119 120 func setIDUnsigned(ctx context.Context, dbName, table string, tx *sql.DB) error { 121 logger.Sugar().Infow( 122 "setIDUnsigned", 123 "db", dbName, 124 "table", table, 125 "State", "INT UNSIGNED", 126 ) 127 _, err := tx.ExecContext( 128 ctx, 129 fmt.Sprintf("alter table %v.%v change id id int unsigned not null auto_increment", dbName, table), 130 ) 131 if err != nil { 132 return err 133 } 134 return nil 135 } 136 137 func existEntIDUnique(ctx context.Context, dbName, table string, tx *sql.DB) (bool, bool, error) { 138 key := "" 139 rc := 0 140 rows, err := tx.QueryContext( 141 ctx, 142 fmt.Sprintf("select column_key,1 from information_schema.columns where table_schema='%v' and table_name='%v' and column_name='ent_id'", dbName, table), 143 ) 144 if err != nil { 145 return false, false, err 146 } 147 for rows.Next() { 148 if err := rows.Scan(&key, &rc); err != nil { 149 return false, false, err 150 } 151 } 152 return rc == 1, key == "UNI", nil 153 } 154 155 func setEmptyEntID(ctx context.Context, dbName, table string, tx *sql.DB) error { 156 logger.Sugar().Infow( 157 "setEmptyEntID", 158 "db", dbName, 159 "table", table, 160 "State", "ENT_ID UUID", 161 ) 162 rows, err := tx.QueryContext( 163 ctx, 164 fmt.Sprintf("select id, ent_id from %v.%v", dbName, table), 165 ) 166 if err != nil { 167 return err 168 } 169 for rows.Next() { 170 var id uint32 171 var entID string 172 if err := rows.Scan(&id, &entID); err != nil { 173 return err 174 } 175 if _, err := uuid.Parse(entID); err == nil { 176 continue 177 } 178 if _, err := tx.ExecContext( 179 ctx, 180 fmt.Sprintf("update %v.%v set ent_id='%v' where id=%v", dbName, table, uuid.New(), id), 181 ); err != nil { 182 return err 183 } 184 } 185 return nil 186 } 187 188 func setEntIDUnique(ctx context.Context, dbName, table string, tx *sql.DB) error { 189 logger.Sugar().Infow( 190 "setEntIDUnique", 191 "db", dbName, 192 "table", table, 193 "State", "ENT_ID UNIQUE", 194 ) 195 _, err := tx.ExecContext( 196 ctx, 197 fmt.Sprintf("alter table %v.%v change column ent_id ent_id char(36) unique", dbName, table), 198 ) 199 if err != nil { 200 return err 201 } 202 return nil 203 } 204 205 func id2EntID(ctx context.Context, dbName, table string, tx *sql.DB) error { 206 logger.Sugar().Infow( 207 "id2EntID", 208 "db", dbName, 209 "table", table, 210 "State", "ID -> EntID", 211 ) 212 _, err := tx.ExecContext( 213 ctx, 214 fmt.Sprintf("alter table %v.%v change column id ent_id char(36) unique", dbName, table), 215 ) 216 if err != nil { 217 return err 218 } 219 return nil 220 } 221 222 func addIDColumn(ctx context.Context, dbName, table string, tx *sql.DB) error { 223 logger.Sugar().Infow( 224 "addIDColumn", 225 "db", dbName, 226 "table", table, 227 "State", "ID INT", 228 ) 229 _, err := tx.ExecContext( 230 ctx, 231 fmt.Sprintf("alter table %v.%v add id int unsigned not null auto_increment, add primary key(id)", dbName, table), 232 ) 233 if err != nil { 234 return err 235 } 236 return nil 237 } 238 239 func dropPrimaryKey(ctx context.Context, dbName, table string, tx *sql.DB) error { 240 logger.Sugar().Infow( 241 "dropPrimaryKey", 242 "db", dbName, 243 "table", table, 244 "State", "DROP PRIMARY", 245 ) 246 _, err := tx.ExecContext( 247 ctx, 248 fmt.Sprintf("alter table %v.%v drop primary key", dbName, table), 249 ) 250 if err != nil { 251 logger.Sugar().Warnw( 252 "dropPrimaryKey", 253 "db", dbName, 254 "table", table, 255 "Error", err, 256 ) 257 } 258 return nil 259 } 260 261 func addEntIDColumn(ctx context.Context, dbName, table string, tx *sql.DB) error { 262 logger.Sugar().Infow( 263 "addEntIDColumn", 264 "db", dbName, 265 "table", table, 266 "State", "ADD ENT_ID", 267 ) 268 _, err := tx.ExecContext( 269 ctx, 270 fmt.Sprintf("alter table %v.%v add ent_id char(36) not null", dbName, table), 271 ) 272 if err != nil { 273 return err 274 } 275 return nil 276 } 277 278 func migrateEntID(ctx context.Context, dbName, table string, tx *sql.DB) error { 279 logger.Sugar().Infow( 280 "migrateEntID", 281 "db", dbName, 282 "table", table, 283 ) 284 285 idExist, idInt, idUnsigned, err := existIDInt(ctx, dbName, table, tx) 286 if err != nil { 287 return err 288 } 289 290 if idInt && !idUnsigned { 291 if err := setIDUnsigned(ctx, dbName, table, tx); err != nil { 292 return err 293 } 294 } 295 296 entIDExist, entIDUnique, err := existEntIDUnique(ctx, dbName, table, tx) 297 if err != nil { 298 return err 299 } 300 301 if entIDExist && idInt { 302 if err := setEmptyEntID(ctx, dbName, table, tx); err != nil { 303 return err 304 } 305 if !entIDUnique { 306 if err := setEntIDUnique(ctx, dbName, table, tx); err != nil { 307 return err 308 } 309 } 310 return nil 311 } 312 if idExist && !idInt { 313 if err := id2EntID(ctx, dbName, table, tx); err != nil { 314 return err 315 } 316 } 317 if !idInt { 318 if err := dropPrimaryKey(ctx, dbName, table, tx); err != nil { 319 return err 320 } 321 if err := addIDColumn(ctx, dbName, table, tx); err != nil { 322 return err 323 } 324 } 325 326 entIDExist, _, err = existEntIDUnique(ctx, dbName, table, tx) 327 if err != nil { 328 return err 329 } 330 if !entIDExist { 331 if err := addEntIDColumn(ctx, dbName, table, tx); err != nil { 332 return err 333 } 334 } 335 if err := setEmptyEntID(ctx, dbName, table, tx); err != nil { 336 return err 337 } 338 if err := setEntIDUnique(ctx, dbName, table, tx); err != nil { 339 return err 340 } 341 logger.Sugar().Infow( 342 "migrateEntID", 343 "db", dbName, 344 "table", table, 345 "State", "Migrated", 346 ) 347 return err 348 } 349 350 func Migrate(ctx context.Context) error { 351 var err error 352 var conn *sql.DB 353 354 logger.Sugar().Infow("Migrate", "Start", "...") 355 err = redis2.TryLock(lockKey(), 0) 356 if err != nil { 357 return err 358 } 359 defer func(err *error) { 360 _ = redis2.Unlock(lockKey()) 361 logger.Sugar().Infow("Migrate", "Done", "...", "error", *err) 362 }(&err) 363 364 conn, err = open(servicename.ServiceDomain) 365 if err != nil { 366 return err 367 } 368 defer func() { 369 if err := conn.Close(); err != nil { 370 logger.Sugar().Errorw("Close", "Error", err) 371 } 372 }() 373 374 dbname := config.GetStringValueWithNameSpace(servicename.ServiceDomain, keyDBName) 375 _tables, err := tables(ctx, dbname, conn) 376 if err != nil { 377 return err 378 } 379 380 logger.Sugar().Infow( 381 "Migrate", 382 "Round", 1, 383 ) 384 for _, table := range _tables { 385 if err = migrateEntID(ctx, dbname, table, conn); err != nil { 386 return err 387 } 388 } 389 390 logger.Sugar().Infow( 391 "Migrate", 392 "Round", 2, 393 ) 394 for _, table := range _tables { 395 if err = migrateEntID(ctx, dbname, table, conn); err != nil { 396 return err 397 } 398 } 399 return nil 400 }