alby-hub/db/db.go
Roland 66a1be6d16
Fix: sqlite connection parameters, add migrations to delete orphaned records and change postgres transaction amount to bigint (#1462)
* fix: sqlite pragma statement not applying to all sqlite connections

* fix: add migration to delete orphaned records

* fix: migrate postgres transaction amount column to bigint

* chore: simplify migration, update comment

* fix: sqlite uri when running tests

* fix: check that breaks tests

* fix: db locking within transaction

* fix: sqlite3 driver wrapper to execute PRAGMAs on each new connection (#1464)

* fix: sqlite3 driver wrapper to execute PRAGMAs on each new connection

* chore: add test to check temp_store value

---------

Co-authored-by: Roland Bewick <roland.bewick@gmail.com>

---------

Co-authored-by: Roman D <roman@dmitrienko.com>
2025-07-06 17:45:46 +07:00

137 lines
3.5 KiB
Go

package db
import (
"fmt"
"strings"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
gorm_logger "gorm.io/gorm/logger"
"github.com/getAlby/hub/db/migrations"
sqlite_wrapper "github.com/getAlby/hub/db/sqlite-wrapper"
"github.com/getAlby/hub/logger"
)
type Config struct {
URI string
LogQueries bool
DriverName string
}
func NewDB(uri string, logDBQueries bool) (*gorm.DB, error) {
return NewDBWithConfig(&Config{
URI: uri,
LogQueries: logDBQueries,
DriverName: "",
})
}
func NewDBWithConfig(cfg *Config) (*gorm.DB, error) {
gormConfig := &gorm.Config{
TranslateError: true,
}
if cfg.LogQueries {
gormConfig.Logger = gorm_logger.Default.LogMode(gorm_logger.Info)
}
var ret *gorm.DB
if IsPostgresURI(cfg.URI) {
pgConfig := postgres.Config{
DriverName: cfg.DriverName,
DSN: cfg.URI,
}
var err error
ret, err = newPostgresDB(pgConfig, gormConfig)
if err != nil {
return nil, err
}
} else {
sqliteURI := cfg.URI
// apply pragma if we're not running the tests
if !strings.Contains(sqliteURI, "?mode=memory") {
// see https://github.com/mattn/go-sqlite3?tab=readme-ov-file#connection-string
// _txlock: avoid SQLITE_BUSY errors with _txlock=immediate
// _auto_vacuum: properly cleanup disk when deleting records with auto_vacuum=1
// _busy_timeout: avoid SQLITE_BUSY errors with 5 second lock timeout
// _journal_mode: enables write-ahead log so that your reads do not block writes and vice-versa.
// _synchronous: sqlite will sync less frequently and be more performant, still safe to use because of the enabled WAL mode
// _cache_size: 20MB memory cache
sqliteURI = sqliteURI + "?_txlock=immediate&_foreign_keys=1&_auto_vacuum=1&_busy_timeout=5000&_journal_mode=WAL&_synchronous=NORMAL&_cache_size=-20000"
}
driverName := sqlite_wrapper.Sqlite3WrapperDriverName
if cfg.DriverName != "" {
driverName = cfg.DriverName
}
sqliteConfig := sqlite.Config{
DriverName: driverName,
DSN: sqliteURI,
}
var err error
ret, err = newSqliteDB(sqliteConfig, gormConfig)
if err != nil {
return nil, err
}
}
logger.Logger.WithField("db_backend", ret.Dialector.Name()).Debug("loaded database")
err := migrations.Migrate(ret)
if err != nil {
logger.Logger.WithError(err).Error("Failed to migrate")
return nil, err
}
return ret, nil
}
func newSqliteDB(sqliteConfig sqlite.Config, gormConfig *gorm.Config) (*gorm.DB, error) {
gormDB, err := gorm.Open(sqlite.New(sqliteConfig), gormConfig)
if err != nil {
return nil, err
}
return gormDB, nil
}
func newPostgresDB(pgConfig postgres.Config, gormConfig *gorm.Config) (*gorm.DB, error) {
gormDB, err := gorm.Open(postgres.New(pgConfig), gormConfig)
if err != nil {
return nil, err
}
return gormDB, nil
}
func Stop(db *gorm.DB) error {
sqlDB, err := db.DB()
if err != nil {
return fmt.Errorf("failed to get database connection: %w", err)
}
dbBackend := db.Dialector.Name()
logger.Logger.WithField("db_backend", dbBackend).Debug("shutting down database")
if dbBackend == "sqlite" {
err = db.Exec("PRAGMA wal_checkpoint(FULL)", nil).Error
if err != nil {
logger.Logger.WithError(err).Error("Failed to execute wal endpoint")
}
}
err = sqlDB.Close()
if err != nil {
return fmt.Errorf("failed to close database connection: %w", err)
}
return nil
}
func IsPostgresURI(uri string) bool {
return strings.HasPrefix(uri, "postgresql://") ||
strings.HasPrefix(uri, "postgres://") // Schema used by the "testdb" package.
}