109 lines
2.2 KiB
Go
109 lines
2.2 KiB
Go
package database
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
|
|
"gorm.io/plugin/opentelemetry/tracing"
|
|
)
|
|
|
|
var (
|
|
db *gorm.DB
|
|
once sync.Once
|
|
)
|
|
|
|
// Connect initializes the database connection (singleton pattern).
|
|
// If dsn starts with postgres:// or postgresql://, connects to PostgreSQL.
|
|
// Otherwise falls back to SQLite (development only).
|
|
func Connect(dsn string) (*gorm.DB, error) {
|
|
var err error
|
|
|
|
once.Do(func() {
|
|
config := &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Warn),
|
|
}
|
|
|
|
isPostgres := strings.HasPrefix(dsn, "postgres://") ||
|
|
strings.HasPrefix(dsn, "postgresql://")
|
|
|
|
if isPostgres {
|
|
db, err = gorm.Open(postgres.Open(dsn), config)
|
|
} else {
|
|
db, err = gorm.Open(sqlite.Open(dsn), config)
|
|
}
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to connect to database: %w", err)
|
|
return
|
|
}
|
|
|
|
if pluginErr := db.Use(tracing.NewPlugin(
|
|
tracing.WithoutMetrics(),
|
|
tracing.WithoutQueryVariables(),
|
|
)); pluginErr != nil {
|
|
err = fmt.Errorf("failed to register otel plugin: %w", pluginErr)
|
|
return
|
|
}
|
|
|
|
// Configure connection pool for PostgreSQL.
|
|
if isPostgres {
|
|
sqlDB, poolErr := db.DB()
|
|
if poolErr != nil {
|
|
err = fmt.Errorf("failed to get sql.DB: %w", poolErr)
|
|
return
|
|
}
|
|
sqlDB.SetMaxOpenConns(25)
|
|
sqlDB.SetMaxIdleConns(5)
|
|
sqlDB.SetConnMaxLifetime(5 * time.Minute)
|
|
}
|
|
|
|
log.Println("Database connection established")
|
|
})
|
|
|
|
return db, err
|
|
}
|
|
|
|
// GetDB returns the database instance (must call Connect first)
|
|
func GetDB() *gorm.DB {
|
|
if db == nil {
|
|
log.Fatal("Database not initialized. Call Connect() first.")
|
|
}
|
|
return db
|
|
}
|
|
|
|
// Close closes the database connection
|
|
func Close() error {
|
|
if db == nil {
|
|
return nil
|
|
}
|
|
|
|
sqlDB, err := db.DB()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get database instance: %w", err)
|
|
}
|
|
|
|
if err := sqlDB.Close(); err != nil {
|
|
return fmt.Errorf("failed to close database: %w", err)
|
|
}
|
|
|
|
log.Println("Database connection closed")
|
|
return nil
|
|
}
|
|
|
|
// ResetForTesting resets the singleton for testing purposes
|
|
// WARNING: Only use this in tests!
|
|
func ResetForTesting() {
|
|
if db != nil {
|
|
_ = Close()
|
|
db = nil
|
|
once = sync.Once{}
|
|
}
|
|
}
|