2018-06-07 14:54:36 -05:00
package sqlstore
import (
"context"
2022-10-17 13:23:44 -05:00
"errors"
2018-06-07 14:54:36 -05:00
"reflect"
2022-10-17 13:23:44 -05:00
"time"
2018-06-07 14:54:36 -05:00
2020-04-01 08:57:21 -05:00
"xorm.io/xorm"
2022-02-08 08:02:23 -06:00
"github.com/grafana/grafana/pkg/infra/log"
2022-10-17 13:23:44 -05:00
"github.com/mattn/go-sqlite3"
2018-06-07 14:54:36 -05:00
)
2022-02-08 08:02:23 -06:00
var sessionLogger = log . New ( "sqlstore.session" )
2018-06-07 14:54:36 -05:00
type DBSession struct {
* xorm . Session
2022-02-08 08:02:23 -06:00
transactionOpen bool
events [ ] interface { }
2018-06-07 14:54:36 -05:00
}
2022-02-01 07:51:22 -06:00
type DBTransactionFunc func ( sess * DBSession ) error
2018-06-07 14:54:36 -05:00
func ( sess * DBSession ) publishAfterCommit ( msg interface { } ) {
sess . events = append ( sess . events , msg )
2022-06-28 07:32:25 -05:00
}
func ( sess * DBSession ) PublishAfterCommit ( msg interface { } ) {
sess . events = append ( sess . events , msg )
2018-06-07 14:54:36 -05:00
}
2022-02-08 08:02:23 -06:00
func startSessionOrUseExisting ( ctx context . Context , engine * xorm . Engine , beginTran bool ) ( * DBSession , bool , error ) {
2020-03-23 07:37:53 -05:00
value := ctx . Value ( ContextSessionKey { } )
2018-06-07 14:54:36 -05:00
var sess * DBSession
sess , ok := value . ( * DBSession )
2018-06-18 07:50:36 -05:00
if ok {
2022-09-20 11:32:06 -05:00
ctxLogger := sessionLogger . FromContext ( ctx )
ctxLogger . Debug ( "reusing existing session" , "transaction" , sess . transactionOpen )
2021-05-27 06:55:33 -05:00
sess . Session = sess . Session . Context ( ctx )
2022-02-08 08:02:23 -06:00
return sess , false , nil
2018-06-07 14:54:36 -05:00
}
2022-02-08 08:02:23 -06:00
newSess := & DBSession { Session : engine . NewSession ( ) , transactionOpen : beginTran }
2018-06-18 07:50:36 -05:00
if beginTran {
err := newSess . Begin ( )
if err != nil {
2022-02-08 08:02:23 -06:00
return nil , false , err
2018-06-18 07:50:36 -05:00
}
}
2021-05-27 06:55:33 -05:00
newSess . Session = newSess . Session . Context ( ctx )
2022-09-29 07:55:47 -05:00
2022-02-08 08:02:23 -06:00
return newSess , true , nil
2018-06-07 14:54:36 -05:00
}
2022-09-29 07:55:47 -05:00
// WithDbSession calls the callback with the session in the context (if exists).
// Otherwise it creates a new one that is closed upon completion.
// A session is stored in the context if sqlstore.InTransaction() has been been previously called with the same context (and it's not committed/rolledback yet).
2022-10-17 13:23:44 -05:00
// In case of sqlite3.ErrLocked or sqlite3.ErrBusy failure it will be retried at most five times before giving up.
2022-02-01 07:51:22 -06:00
func ( ss * SQLStore ) WithDbSession ( ctx context . Context , callback DBTransactionFunc ) error {
2022-10-17 13:23:44 -05:00
return ss . withDbSession ( ctx , ss . engine , callback )
2019-05-16 05:39:59 -05:00
}
2022-09-29 07:55:47 -05:00
// WithNewDbSession calls the callback with a new session that is closed upon completion.
2022-10-17 13:23:44 -05:00
// In case of sqlite3.ErrLocked or sqlite3.ErrBusy failure it will be retried at most five times before giving up.
2022-09-29 07:55:47 -05:00
func ( ss * SQLStore ) WithNewDbSession ( ctx context . Context , callback DBTransactionFunc ) error {
sess := & DBSession { Session : ss . engine . NewSession ( ) , transactionOpen : false }
defer sess . Close ( )
2022-10-17 13:23:44 -05:00
return ss . withRetry ( ctx , callback , 0 ) ( sess )
2022-09-29 07:55:47 -05:00
}
2022-10-17 13:23:44 -05:00
func ( ss * SQLStore ) withRetry ( ctx context . Context , callback DBTransactionFunc , retry int ) DBTransactionFunc {
return func ( sess * DBSession ) error {
err := callback ( sess )
ctxLogger := tsclogger . FromContext ( ctx )
var sqlError sqlite3 . Error
if errors . As ( err , & sqlError ) && retry < ss . dbCfg . QueryRetries && ( sqlError . Code == sqlite3 . ErrLocked || sqlError . Code == sqlite3 . ErrBusy ) {
time . Sleep ( time . Millisecond * time . Duration ( 10 ) )
ctxLogger . Info ( "Database locked, sleeping then retrying" , "error" , err , "retry" , retry , "code" , sqlError . Code )
return ss . withRetry ( ctx , callback , retry + 1 ) ( sess )
}
return err
}
}
func ( ss * SQLStore ) withDbSession ( ctx context . Context , engine * xorm . Engine , callback DBTransactionFunc ) error {
2022-02-08 08:02:23 -06:00
sess , isNew , err := startSessionOrUseExisting ( ctx , engine , false )
if err != nil {
return err
}
if isNew {
defer sess . Close ( )
}
2022-10-17 13:23:44 -05:00
return ss . withRetry ( ctx , callback , 0 ) ( sess )
2018-06-07 14:54:36 -05:00
}
func ( sess * DBSession ) InsertId ( bean interface { } ) ( int64 , error ) {
table := sess . DB ( ) . Mapper . Obj2Table ( getTypeName ( bean ) )
2019-10-22 07:08:18 -05:00
if err := dialect . PreInsertId ( table , sess . Session ) ; err != nil {
return 0 , err
}
2018-06-07 14:54:36 -05:00
id , err := sess . Session . InsertOne ( bean )
2019-10-22 07:08:18 -05:00
if err != nil {
return 0 , err
}
if err := dialect . PostInsertId ( table , sess . Session ) ; err != nil {
return 0 , err
}
2018-06-07 14:54:36 -05:00
2019-10-22 07:08:18 -05:00
return id , nil
2018-06-07 14:54:36 -05:00
}
func getTypeName ( bean interface { } ) ( res string ) {
t := reflect . TypeOf ( bean )
for t . Kind ( ) == reflect . Ptr {
t = t . Elem ( )
}
return t . Name ( )
}