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  }