github.com/shoshinnikita/budget-manager@v0.7.1-0.20220131195411-8c46ff1c6778/internal/db/base/base.go (about) 1 // package base provides a db-agnostic implementation of the storage 2 package base 3 4 import ( 5 "context" 6 "time" 7 8 "github.com/ShoshinNikita/budget-manager/internal/db/base/internal/migrator" 9 "github.com/ShoshinNikita/budget-manager/internal/db/base/internal/sqlx" 10 "github.com/ShoshinNikita/budget-manager/internal/logger" 11 "github.com/ShoshinNikita/budget-manager/internal/pkg/errors" 12 ) 13 14 const ( 15 Question = sqlx.Question 16 Dollar = sqlx.Dollar 17 ) 18 19 type DB struct { 20 db *sqlx.DB 21 } 22 23 // NewDB creates a new connection to the db and applies the migrations 24 func NewDB(driverName, dataSourceName string, placeholder sqlx.Placeholder, 25 migrations []*migrator.Migration, log logger.Logger) (*DB, error) { 26 27 conn, err := sqlx.Open(driverName, dataSourceName, placeholder, log) 28 if err != nil { 29 return nil, errors.Wrap(err, "couldn't open a connection to db") 30 } 31 32 ctx := context.Background() 33 34 if err := pingDB(ctx, conn, log); err != nil { 35 return nil, err 36 } 37 if err := applyMigrations(ctx, conn, migrations, log); err != nil { 38 return nil, err 39 } 40 41 return &DB{conn}, nil 42 } 43 44 const ( 45 pingRetries = 10 46 pingRetryTimeout = 500 * time.Millisecond 47 ) 48 49 func pingDB(ctx context.Context, db *sqlx.DB, log logger.Logger) error { 50 for i := 0; i < pingRetries; i++ { 51 err := db.Ping(ctx) 52 if err == nil { 53 break 54 } 55 56 log.WithError(err).WithField("try", i+1).Debug("couldn't ping DB") 57 if i+1 == pingRetries { 58 // Don't sleep extra time 59 return errors.New("database is down") 60 } 61 62 time.Sleep(pingRetryTimeout) 63 } 64 return nil 65 } 66 67 func applyMigrations(ctx context.Context, db *sqlx.DB, migrations []*migrator.Migration, log logger.Logger) error { 68 migrator, err := migrator.NewMigrator(db.GetInternalDB(), log, migrations) 69 if err != nil { 70 return errors.Wrap(err, "couldn't prepare migrator") 71 } 72 73 if err := migrator.Migrate(ctx); err != nil { 74 return errors.Wrap(err, "couldn't apply migrations") 75 } 76 77 return nil 78 } 79 80 // Shutdown closes the connection to the db 81 func (db *DB) Shutdown() error { 82 return db.db.Close() 83 }