first commit
This commit is contained in:
@@ -0,0 +1,495 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// Config holds database configuration settings
|
||||
type Config struct {
|
||||
// Database file path
|
||||
DatabasePath string
|
||||
|
||||
// Connection pool settings
|
||||
MaxIdleConns int
|
||||
MaxOpenConns int
|
||||
ConnMaxLifetime time.Duration
|
||||
ConnMaxIdleTime time.Duration
|
||||
|
||||
// Logging settings
|
||||
LogLevel logger.LogLevel
|
||||
SlowQueryLog time.Duration
|
||||
|
||||
// Migration settings
|
||||
AutoMigrate bool
|
||||
DropTableFirst bool
|
||||
CreateBatchSize int
|
||||
|
||||
// Performance settings
|
||||
PrepareStmt bool
|
||||
DisableForeignKeyCheck bool
|
||||
IgnoreRelationshipsWhenMigrating bool
|
||||
|
||||
// Development settings
|
||||
Debug bool
|
||||
DryRun bool
|
||||
QueryFields bool
|
||||
CreateInBatches int
|
||||
}
|
||||
|
||||
// DefaultConfig returns a configuration with sensible defaults
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
DatabasePath: "fuel_stops.db",
|
||||
MaxIdleConns: 10,
|
||||
MaxOpenConns: 100,
|
||||
ConnMaxLifetime: time.Hour,
|
||||
ConnMaxIdleTime: 30 * time.Minute,
|
||||
LogLevel: logger.Silent,
|
||||
SlowQueryLog: 200 * time.Millisecond,
|
||||
AutoMigrate: true,
|
||||
DropTableFirst: false,
|
||||
CreateBatchSize: 1000,
|
||||
PrepareStmt: true,
|
||||
DisableForeignKeyCheck: false,
|
||||
IgnoreRelationshipsWhenMigrating: false,
|
||||
Debug: false,
|
||||
DryRun: false,
|
||||
QueryFields: false,
|
||||
CreateInBatches: 100,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadFromConfig loads configuration from config file using Viper
|
||||
func LoadFromConfig(configPath string) *Config {
|
||||
config := DefaultConfig()
|
||||
|
||||
// Initialize Viper
|
||||
v := viper.New()
|
||||
|
||||
// Set config file path if provided
|
||||
if configPath != "" {
|
||||
v.SetConfigFile(configPath)
|
||||
} else {
|
||||
// Search for config file in multiple locations
|
||||
v.SetConfigName("config")
|
||||
v.SetConfigType("yaml")
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath("./config")
|
||||
v.AddConfigPath("$HOME/.tankstopp")
|
||||
v.AddConfigPath("/etc/tankstopp")
|
||||
}
|
||||
|
||||
// Try to read config file
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
// If config file not found, fall back to environment variables
|
||||
return LoadFromEnv()
|
||||
}
|
||||
|
||||
// Load database configuration from Viper
|
||||
if v.IsSet("database.path") {
|
||||
config.DatabasePath = v.GetString("database.path")
|
||||
}
|
||||
|
||||
// Connection pool settings
|
||||
if v.IsSet("database.connection_pool.max_idle_connections") {
|
||||
config.MaxIdleConns = v.GetInt("database.connection_pool.max_idle_connections")
|
||||
}
|
||||
if v.IsSet("database.connection_pool.max_open_connections") {
|
||||
config.MaxOpenConns = v.GetInt("database.connection_pool.max_open_connections")
|
||||
}
|
||||
if v.IsSet("database.connection_pool.connection_max_lifetime") {
|
||||
config.ConnMaxLifetime = v.GetDuration("database.connection_pool.connection_max_lifetime")
|
||||
}
|
||||
if v.IsSet("database.connection_pool.connection_max_idle_time") {
|
||||
config.ConnMaxIdleTime = v.GetDuration("database.connection_pool.connection_max_idle_time")
|
||||
}
|
||||
|
||||
// Logging settings
|
||||
if v.IsSet("database.logging.level") {
|
||||
config.LogLevel = getLogLevelFromString(v.GetString("database.logging.level"))
|
||||
}
|
||||
if v.IsSet("database.logging.debug") {
|
||||
config.Debug = v.GetBool("database.logging.debug")
|
||||
}
|
||||
if v.IsSet("database.logging.slow_query_threshold") {
|
||||
config.SlowQueryLog = v.GetDuration("database.logging.slow_query_threshold")
|
||||
}
|
||||
|
||||
// Migration settings
|
||||
if v.IsSet("database.migration.auto_migrate") {
|
||||
config.AutoMigrate = v.GetBool("database.migration.auto_migrate")
|
||||
}
|
||||
if v.IsSet("database.migration.drop_tables_first") {
|
||||
config.DropTableFirst = v.GetBool("database.migration.drop_tables_first")
|
||||
}
|
||||
if v.IsSet("database.migration.create_batch_size") {
|
||||
config.CreateBatchSize = v.GetInt("database.migration.create_batch_size")
|
||||
}
|
||||
|
||||
// Performance settings
|
||||
if v.IsSet("database.performance.prepare_statements") {
|
||||
config.PrepareStmt = v.GetBool("database.performance.prepare_statements")
|
||||
}
|
||||
if v.IsSet("database.performance.disable_foreign_key_check") {
|
||||
config.DisableForeignKeyCheck = v.GetBool("database.performance.disable_foreign_key_check")
|
||||
}
|
||||
if v.IsSet("database.performance.ignore_relationships_when_migrating") {
|
||||
config.IgnoreRelationshipsWhenMigrating = v.GetBool("database.performance.ignore_relationships_when_migrating")
|
||||
}
|
||||
if v.IsSet("database.performance.query_fields") {
|
||||
config.QueryFields = v.GetBool("database.performance.query_fields")
|
||||
}
|
||||
if v.IsSet("database.performance.dry_run") {
|
||||
config.DryRun = v.GetBool("database.performance.dry_run")
|
||||
}
|
||||
if v.IsSet("database.performance.create_in_batches") {
|
||||
config.CreateInBatches = v.GetInt("database.performance.create_in_batches")
|
||||
}
|
||||
|
||||
// Environment variables still take precedence over config file
|
||||
config = mergeWithEnvVars(config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// LoadFromEnv loads configuration from environment variables
|
||||
func LoadFromEnv() *Config {
|
||||
config := DefaultConfig()
|
||||
|
||||
// Database path
|
||||
if dbPath := os.Getenv("DB_PATH"); dbPath != "" {
|
||||
config.DatabasePath = dbPath
|
||||
}
|
||||
|
||||
// Connection pool settings
|
||||
if maxIdle := getEnvInt("DB_MAX_IDLE_CONNS", config.MaxIdleConns); maxIdle > 0 {
|
||||
config.MaxIdleConns = maxIdle
|
||||
}
|
||||
|
||||
if maxOpen := getEnvInt("DB_MAX_OPEN_CONNS", config.MaxOpenConns); maxOpen > 0 {
|
||||
config.MaxOpenConns = maxOpen
|
||||
}
|
||||
|
||||
if lifetime := getEnvDuration("DB_CONN_MAX_LIFETIME", config.ConnMaxLifetime); lifetime > 0 {
|
||||
config.ConnMaxLifetime = lifetime
|
||||
}
|
||||
|
||||
if idleTime := getEnvDuration("DB_CONN_MAX_IDLE_TIME", config.ConnMaxIdleTime); idleTime > 0 {
|
||||
config.ConnMaxIdleTime = idleTime
|
||||
}
|
||||
|
||||
// Logging settings
|
||||
config.LogLevel = getLogLevel()
|
||||
config.Debug = getEnvBool("DB_DEBUG", config.Debug)
|
||||
|
||||
if slowLog := getEnvDuration("DB_SLOW_QUERY_LOG", config.SlowQueryLog); slowLog > 0 {
|
||||
config.SlowQueryLog = slowLog
|
||||
}
|
||||
|
||||
// Migration settings
|
||||
config.AutoMigrate = getEnvBool("DB_AUTO_MIGRATE", config.AutoMigrate)
|
||||
config.DropTableFirst = getEnvBool("DB_DROP_TABLE_FIRST", config.DropTableFirst)
|
||||
|
||||
if batchSize := getEnvInt("DB_CREATE_BATCH_SIZE", config.CreateBatchSize); batchSize > 0 {
|
||||
config.CreateBatchSize = batchSize
|
||||
}
|
||||
|
||||
// Performance settings
|
||||
config.PrepareStmt = getEnvBool("DB_PREPARE_STMT", config.PrepareStmt)
|
||||
config.DisableForeignKeyCheck = getEnvBool("DB_DISABLE_FOREIGN_KEY_CHECK", config.DisableForeignKeyCheck)
|
||||
config.IgnoreRelationshipsWhenMigrating = getEnvBool("DB_IGNORE_RELATIONSHIPS_WHEN_MIGRATING", config.IgnoreRelationshipsWhenMigrating)
|
||||
|
||||
// Development settings
|
||||
config.DryRun = getEnvBool("DB_DRY_RUN", config.DryRun)
|
||||
config.QueryFields = getEnvBool("DB_QUERY_FIELDS", config.QueryFields)
|
||||
|
||||
if inBatches := getEnvInt("DB_CREATE_IN_BATCHES", config.CreateInBatches); inBatches > 0 {
|
||||
config.CreateInBatches = inBatches
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// mergeWithEnvVars merges environment variables into existing config
|
||||
// Environment variables take precedence over config file values
|
||||
func mergeWithEnvVars(config *Config) *Config {
|
||||
// Database path
|
||||
if dbPath := os.Getenv("DB_PATH"); dbPath != "" {
|
||||
config.DatabasePath = dbPath
|
||||
}
|
||||
|
||||
// Connection pool settings
|
||||
if maxIdle := getEnvInt("DB_MAX_IDLE_CONNS", config.MaxIdleConns); maxIdle > 0 {
|
||||
config.MaxIdleConns = maxIdle
|
||||
}
|
||||
|
||||
if maxOpen := getEnvInt("DB_MAX_OPEN_CONNS", config.MaxOpenConns); maxOpen > 0 {
|
||||
config.MaxOpenConns = maxOpen
|
||||
}
|
||||
|
||||
if lifetime := getEnvDuration("DB_CONN_MAX_LIFETIME", config.ConnMaxLifetime); lifetime > 0 {
|
||||
config.ConnMaxLifetime = lifetime
|
||||
}
|
||||
|
||||
if idleTime := getEnvDuration("DB_CONN_MAX_IDLE_TIME", config.ConnMaxIdleTime); idleTime > 0 {
|
||||
config.ConnMaxIdleTime = idleTime
|
||||
}
|
||||
|
||||
// Logging settings
|
||||
if envLogLevel := getLogLevel(); envLogLevel != logger.Silent {
|
||||
config.LogLevel = envLogLevel
|
||||
}
|
||||
if envDebug := os.Getenv("DB_DEBUG"); envDebug != "" {
|
||||
config.Debug = getEnvBool("DB_DEBUG", config.Debug)
|
||||
}
|
||||
|
||||
if slowLog := getEnvDuration("DB_SLOW_QUERY_LOG", config.SlowQueryLog); slowLog > 0 {
|
||||
config.SlowQueryLog = slowLog
|
||||
}
|
||||
|
||||
// Migration settings
|
||||
if envAutoMigrate := os.Getenv("DB_AUTO_MIGRATE"); envAutoMigrate != "" {
|
||||
config.AutoMigrate = getEnvBool("DB_AUTO_MIGRATE", config.AutoMigrate)
|
||||
}
|
||||
if envDropFirst := os.Getenv("DB_DROP_TABLE_FIRST"); envDropFirst != "" {
|
||||
config.DropTableFirst = getEnvBool("DB_DROP_TABLE_FIRST", config.DropTableFirst)
|
||||
}
|
||||
|
||||
if batchSize := getEnvInt("DB_CREATE_BATCH_SIZE", config.CreateBatchSize); batchSize > 0 {
|
||||
config.CreateBatchSize = batchSize
|
||||
}
|
||||
|
||||
// Performance settings
|
||||
if envPrepare := os.Getenv("DB_PREPARE_STMT"); envPrepare != "" {
|
||||
config.PrepareStmt = getEnvBool("DB_PREPARE_STMT", config.PrepareStmt)
|
||||
}
|
||||
if envFKCheck := os.Getenv("DB_DISABLE_FOREIGN_KEY_CHECK"); envFKCheck != "" {
|
||||
config.DisableForeignKeyCheck = getEnvBool("DB_DISABLE_FOREIGN_KEY_CHECK", config.DisableForeignKeyCheck)
|
||||
}
|
||||
if envIgnoreRel := os.Getenv("DB_IGNORE_RELATIONSHIPS_WHEN_MIGRATING"); envIgnoreRel != "" {
|
||||
config.IgnoreRelationshipsWhenMigrating = getEnvBool("DB_IGNORE_RELATIONSHIPS_WHEN_MIGRATING", config.IgnoreRelationshipsWhenMigrating)
|
||||
}
|
||||
|
||||
// Development settings
|
||||
if envDryRun := os.Getenv("DB_DRY_RUN"); envDryRun != "" {
|
||||
config.DryRun = getEnvBool("DB_DRY_RUN", config.DryRun)
|
||||
}
|
||||
if envQueryFields := os.Getenv("DB_QUERY_FIELDS"); envQueryFields != "" {
|
||||
config.QueryFields = getEnvBool("DB_QUERY_FIELDS", config.QueryFields)
|
||||
}
|
||||
|
||||
if inBatches := getEnvInt("DB_CREATE_IN_BATCHES", config.CreateInBatches); inBatches > 0 {
|
||||
config.CreateInBatches = inBatches
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// Validate checks if the configuration is valid
|
||||
func (c *Config) Validate() error {
|
||||
if c.DatabasePath == "" {
|
||||
return fmt.Errorf("database path cannot be empty")
|
||||
}
|
||||
|
||||
if c.MaxIdleConns < 0 {
|
||||
return fmt.Errorf("max idle connections cannot be negative")
|
||||
}
|
||||
|
||||
if c.MaxOpenConns < 0 {
|
||||
return fmt.Errorf("max open connections cannot be negative")
|
||||
}
|
||||
|
||||
if c.MaxIdleConns > c.MaxOpenConns && c.MaxOpenConns > 0 {
|
||||
return fmt.Errorf("max idle connections (%d) cannot be greater than max open connections (%d)",
|
||||
c.MaxIdleConns, c.MaxOpenConns)
|
||||
}
|
||||
|
||||
if c.ConnMaxLifetime < 0 {
|
||||
return fmt.Errorf("connection max lifetime cannot be negative")
|
||||
}
|
||||
|
||||
if c.ConnMaxIdleTime < 0 {
|
||||
return fmt.Errorf("connection max idle time cannot be negative")
|
||||
}
|
||||
|
||||
if c.SlowQueryLog < 0 {
|
||||
return fmt.Errorf("slow query log threshold cannot be negative")
|
||||
}
|
||||
|
||||
if c.CreateBatchSize <= 0 {
|
||||
return fmt.Errorf("create batch size must be greater than 0")
|
||||
}
|
||||
|
||||
if c.CreateInBatches <= 0 {
|
||||
return fmt.Errorf("create in batches size must be greater than 0")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a string representation of the configuration
|
||||
func (c *Config) String() string {
|
||||
return fmt.Sprintf(`Database Configuration:
|
||||
Database Path: %s
|
||||
Max Idle Connections: %d
|
||||
Max Open Connections: %d
|
||||
Connection Max Lifetime: %v
|
||||
Connection Max Idle Time: %v
|
||||
Log Level: %v
|
||||
Slow Query Log Threshold: %v
|
||||
Auto Migrate: %t
|
||||
Prepare Statements: %t
|
||||
Debug Mode: %t
|
||||
Dry Run: %t
|
||||
Create Batch Size: %d
|
||||
Create In Batches: %d`,
|
||||
c.DatabasePath,
|
||||
c.MaxIdleConns,
|
||||
c.MaxOpenConns,
|
||||
c.ConnMaxLifetime,
|
||||
c.ConnMaxIdleTime,
|
||||
c.LogLevel,
|
||||
c.SlowQueryLog,
|
||||
c.AutoMigrate,
|
||||
c.PrepareStmt,
|
||||
c.Debug,
|
||||
c.DryRun,
|
||||
c.CreateBatchSize,
|
||||
c.CreateInBatches,
|
||||
)
|
||||
}
|
||||
|
||||
// IsProduction returns true if running in production environment
|
||||
func (c *Config) IsProduction() bool {
|
||||
env := os.Getenv("ENV")
|
||||
return env == "production" || env == "prod"
|
||||
}
|
||||
|
||||
// IsDevelopment returns true if running in development environment
|
||||
func (c *Config) IsDevelopment() bool {
|
||||
env := os.Getenv("ENV")
|
||||
return env == "development" || env == "dev" || env == ""
|
||||
}
|
||||
|
||||
// IsTest returns true if running in test environment
|
||||
func (c *Config) IsTest() bool {
|
||||
env := os.Getenv("ENV")
|
||||
return env == "test" || env == "testing"
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func getEnvInt(key string, defaultValue int) int {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
if intValue, err := strconv.Atoi(value); err == nil {
|
||||
return intValue
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getEnvBool(key string, defaultValue bool) bool {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
if boolValue, err := strconv.ParseBool(value); err == nil {
|
||||
return boolValue
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getEnvDuration(key string, defaultValue time.Duration) time.Duration {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
if duration, err := time.ParseDuration(value); err == nil {
|
||||
return duration
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getLogLevel() logger.LogLevel {
|
||||
debug := getEnvBool("DB_DEBUG", false)
|
||||
env := os.Getenv("ENV")
|
||||
logLevel := os.Getenv("DB_LOG_LEVEL")
|
||||
|
||||
switch {
|
||||
case debug:
|
||||
return logger.Info
|
||||
case env == "development" || env == "dev":
|
||||
return logger.Warn
|
||||
case env == "test" || env == "testing":
|
||||
return logger.Silent
|
||||
case logLevel == "silent":
|
||||
return logger.Silent
|
||||
case logLevel == "error":
|
||||
return logger.Error
|
||||
case logLevel == "warn":
|
||||
return logger.Warn
|
||||
case logLevel == "info":
|
||||
return logger.Info
|
||||
default:
|
||||
return logger.Silent
|
||||
}
|
||||
}
|
||||
|
||||
// getLogLevelFromString converts string log level to GORM logger level
|
||||
func getLogLevelFromString(level string) logger.LogLevel {
|
||||
switch strings.ToLower(level) {
|
||||
case "silent":
|
||||
return logger.Silent
|
||||
case "error":
|
||||
return logger.Error
|
||||
case "warn", "warning":
|
||||
return logger.Warn
|
||||
case "info":
|
||||
return logger.Info
|
||||
default:
|
||||
return logger.Silent
|
||||
}
|
||||
}
|
||||
|
||||
// Environment variable documentation
|
||||
/*
|
||||
Available Environment Variables:
|
||||
|
||||
Database Settings:
|
||||
DB_PATH - Database file path (default: "fuel_stops.db")
|
||||
DB_AUTO_MIGRATE - Enable automatic migrations (default: true)
|
||||
DB_DROP_TABLE_FIRST - Drop tables before migration (default: false)
|
||||
|
||||
Connection Pool Settings:
|
||||
DB_MAX_IDLE_CONNS - Maximum idle connections (default: 10)
|
||||
DB_MAX_OPEN_CONNS - Maximum open connections (default: 100)
|
||||
DB_CONN_MAX_LIFETIME - Connection maximum lifetime (default: "1h")
|
||||
DB_CONN_MAX_IDLE_TIME - Connection maximum idle time (default: "30m")
|
||||
|
||||
Logging Settings:
|
||||
DB_DEBUG - Enable debug logging (default: false)
|
||||
DB_LOG_LEVEL - Log level: silent, error, warn, info (default: silent)
|
||||
DB_SLOW_QUERY_LOG - Slow query threshold (default: "200ms")
|
||||
|
||||
Performance Settings:
|
||||
DB_PREPARE_STMT - Use prepared statements (default: true)
|
||||
DB_CREATE_BATCH_SIZE - Batch size for migrations (default: 1000)
|
||||
DB_CREATE_IN_BATCHES - Batch size for bulk operations (default: 100)
|
||||
DB_QUERY_FIELDS - Select only required fields (default: false)
|
||||
|
||||
Development Settings:
|
||||
ENV - Environment: development, production, test
|
||||
DB_DRY_RUN - Enable dry run mode (default: false)
|
||||
DB_DISABLE_FOREIGN_KEY_CHECK - Disable FK checks (default: false)
|
||||
DB_IGNORE_RELATIONSHIPS_WHEN_MIGRATING - Ignore relationships in migration (default: false)
|
||||
|
||||
Examples:
|
||||
export DB_DEBUG=true
|
||||
export DB_MAX_OPEN_CONNS=200
|
||||
export DB_CONN_MAX_LIFETIME=2h
|
||||
export DB_LOG_LEVEL=info
|
||||
export ENV=development
|
||||
*/
|
||||
Reference in New Issue
Block a user