2023-02-09 12:16:36 +01:00
|
|
|
package sqlite
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"gitlab.com/wpetit/goweb/logger"
|
2023-03-03 16:37:19 +01:00
|
|
|
|
2023-04-06 14:45:50 +02:00
|
|
|
"modernc.org/sqlite"
|
2023-03-03 16:37:19 +01:00
|
|
|
_ "modernc.org/sqlite"
|
2023-04-06 14:45:50 +02:00
|
|
|
sqlite3 "modernc.org/sqlite/lib"
|
2023-02-09 12:16:36 +01:00
|
|
|
)
|
|
|
|
|
2023-03-03 16:37:19 +01:00
|
|
|
func Open(path string) (*sql.DB, error) {
|
|
|
|
db, err := sql.Open("sqlite", path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not open database with path '%s'", path)
|
|
|
|
}
|
|
|
|
|
|
|
|
return db, nil
|
|
|
|
}
|
|
|
|
|
2023-02-09 12:16:36 +01:00
|
|
|
func withTx(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) error {
|
|
|
|
var tx *sql.Tx
|
|
|
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if err := tx.Rollback(); err != nil {
|
|
|
|
if errors.Is(err, sql.ErrTxDone) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
panic(errors.WithStack(err))
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2023-04-06 14:45:50 +02:00
|
|
|
for {
|
|
|
|
if err = fn(tx); err != nil {
|
|
|
|
var sqlErr *sqlite.Error
|
|
|
|
if errors.As(err, &sqlErr) {
|
|
|
|
if sqlErr.Code() == sqlite3.SQLITE_BUSY {
|
|
|
|
logger.Warn(ctx, "database busy, retrying transaction")
|
|
|
|
|
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
logger.Error(ctx, "could not execute transaction", logger.E(errors.WithStack(err)))
|
|
|
|
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
2023-02-09 12:16:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type getDBFunc func(ctx context.Context) (*sql.DB, error)
|
|
|
|
|
|
|
|
func newGetDBFunc(dsn string, initFunc func(ctx context.Context, db *sql.DB) error) getDBFunc {
|
|
|
|
var (
|
|
|
|
db *sql.DB
|
|
|
|
mutex sync.RWMutex
|
|
|
|
)
|
|
|
|
|
|
|
|
return func(ctx context.Context) (*sql.DB, error) {
|
|
|
|
mutex.RLock()
|
|
|
|
if db != nil {
|
|
|
|
defer mutex.RUnlock()
|
|
|
|
|
|
|
|
return db, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex.RUnlock()
|
|
|
|
|
|
|
|
mutex.Lock()
|
|
|
|
defer mutex.Unlock()
|
|
|
|
|
|
|
|
logger.Debug(ctx, "opening database", logger.F("dsn", dsn))
|
|
|
|
|
|
|
|
newDB, err := sql.Open("sqlite", dsn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.Debug(ctx, "initializing database")
|
|
|
|
|
|
|
|
if err = initFunc(ctx, newDB); err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
db = newDB
|
|
|
|
|
|
|
|
return db, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newGetDBFuncFromDB(db *sql.DB, initFunc func(ctx context.Context, db *sql.DB) error) getDBFunc {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
initOnce := &sync.Once{}
|
|
|
|
|
|
|
|
return func(ctx context.Context) (*sql.DB, error) {
|
|
|
|
initOnce.Do(func() {
|
|
|
|
logger.Debug(ctx, "initializing database")
|
|
|
|
|
|
|
|
err = initFunc(ctx, db)
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return db, nil
|
|
|
|
}
|
|
|
|
}
|