github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/pkg/persistence/persistence.go (about)

     1  package persistence
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"time"
     7  
     8  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
     9  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    10  
    11  	"github.com/pkg/errors"
    12  
    13  	// Importing the database driver (postgresql)
    14  	_ "github.com/lib/pq"
    15  
    16  	"github.com/jmoiron/sqlx"
    17  )
    18  
    19  // RetryCount is a number of retries when trying to open the database
    20  const RetryCount int = 50
    21  
    22  // SaveToContext missing godoc
    23  func SaveToContext(ctx context.Context, persistOp PersistenceOp) context.Context {
    24  	return context.WithValue(ctx, PersistenceCtxKey, persistOp)
    25  }
    26  
    27  // FromCtx extracts DatabaseOp interface from context
    28  func FromCtx(ctx context.Context) (PersistenceOp, error) {
    29  	dbCtx := ctx.Value(PersistenceCtxKey)
    30  
    31  	if db, ok := dbCtx.(PersistenceOp); ok {
    32  		return db, nil
    33  	}
    34  
    35  	return nil, apperrors.NewInternalError("unable to fetch database from context")
    36  }
    37  
    38  // Transactioner missing godoc
    39  //go:generate mockery --name=Transactioner --output=automock --outpkg=automock --case=underscore --disable-version-string
    40  type Transactioner interface {
    41  	Begin() (PersistenceTx, error)
    42  	RollbackUnlessCommitted(ctx context.Context, tx PersistenceTx) (didRollback bool)
    43  	PingContext(ctx context.Context) error
    44  	Stats() sql.DBStats
    45  }
    46  
    47  type db struct {
    48  	sqlDB *sqlx.DB
    49  }
    50  
    51  // PingContext missing godoc
    52  func (db *db) PingContext(ctx context.Context) error {
    53  	return db.sqlDB.PingContext(ctx)
    54  }
    55  
    56  // Stats missing godoc
    57  func (db *db) Stats() sql.DBStats {
    58  	return db.sqlDB.Stats()
    59  }
    60  
    61  // Begin missing godoc
    62  func (db *db) Begin() (PersistenceTx, error) {
    63  	tx, err := db.sqlDB.Beginx()
    64  	customTx := &Transaction{
    65  		Tx:        tx,
    66  		committed: false,
    67  	}
    68  	return PersistenceTx(customTx), err
    69  }
    70  
    71  // RollbackUnlessCommitted missing godoc
    72  func (db *db) RollbackUnlessCommitted(ctx context.Context, tx PersistenceTx) (didRollback bool) {
    73  	customTx, ok := tx.(*Transaction)
    74  	if !ok {
    75  		log.C(ctx).Warn("State aware transaction is not in use")
    76  		db.rollback(ctx, tx)
    77  		return true
    78  	}
    79  	if customTx.committed {
    80  		return false
    81  	}
    82  	db.rollback(ctx, customTx)
    83  	return true
    84  }
    85  
    86  func (db *db) rollback(ctx context.Context, tx PersistenceTx) {
    87  	if err := tx.Rollback(); err == nil {
    88  		log.C(ctx).Warn("Transaction rolled back")
    89  	} else if err != sql.ErrTxDone {
    90  		log.C(ctx).Warn(err)
    91  	}
    92  }
    93  
    94  // Transaction missing godoc
    95  type Transaction struct {
    96  	*sqlx.Tx
    97  	committed bool
    98  }
    99  
   100  // Commit missing godoc
   101  func (db *Transaction) Commit() error {
   102  	if db.committed {
   103  		return apperrors.NewInternalError("transaction already committed")
   104  	}
   105  	if err := db.Tx.Commit(); err != nil {
   106  		return errors.Wrap(err, "while committing transaction")
   107  	}
   108  	db.committed = true
   109  	return nil
   110  }
   111  
   112  // PersistenceTx missing godoc
   113  //go:generate mockery --name=PersistenceTx --output=automock --outpkg=automock --case=underscore --disable-version-string
   114  type PersistenceTx interface {
   115  	Commit() error
   116  	Rollback() error
   117  	PersistenceOp
   118  }
   119  
   120  // PersistenceOp missing godoc
   121  //go:generate mockery --name=PersistenceOp --output=automock --outpkg=automock --case=underscore --disable-version-string
   122  type PersistenceOp interface {
   123  	GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
   124  	SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
   125  
   126  	NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error)
   127  	ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
   128  }
   129  
   130  // Configure returns the instance of the database
   131  func Configure(context context.Context, conf DatabaseConfig) (Transactioner, func() error, error) {
   132  	db, closeFunc, err := waitForPersistance(context, conf, RetryCount)
   133  
   134  	return db, closeFunc, err
   135  }
   136  
   137  func waitForPersistance(ctx context.Context, conf DatabaseConfig, retryCount int) (Transactioner, func() error, error) {
   138  	var sqlxDB *sqlx.DB
   139  	var err error
   140  
   141  	for i := 0; i < retryCount; i++ {
   142  		if i > 0 {
   143  			time.Sleep(5 * time.Second)
   144  		}
   145  		log.C(ctx).Info("Trying to connect to DB...")
   146  
   147  		sqlxDB, err = sqlx.Open("postgres", conf.GetConnString())
   148  		if err != nil {
   149  			return nil, nil, err
   150  		}
   151  		ctxWithTimeout, cancelFunc := context.WithTimeout(ctx, time.Second)
   152  		err = sqlxDB.PingContext(ctxWithTimeout)
   153  		cancelFunc()
   154  		if err != nil {
   155  			log.C(ctx).Infof("Got error on pinging DB: %v", err)
   156  			continue
   157  		}
   158  
   159  		log.C(ctx).Infof("Configuring MaxOpenConnections: [%d], MaxIdleConnections: [%d], ConnectionMaxLifetime: [%s]", conf.MaxOpenConnections, conf.MaxIdleConnections, conf.ConnMaxLifetime.String())
   160  		sqlxDB.SetMaxOpenConns(conf.MaxOpenConnections)
   161  		sqlxDB.SetMaxIdleConns(conf.MaxIdleConnections)
   162  		sqlxDB.SetConnMaxLifetime(conf.ConnMaxLifetime)
   163  		return &db{sqlDB: sqlxDB}, sqlxDB.Close, nil
   164  	}
   165  
   166  	return nil, nil, err
   167  }