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 }