Files
ResolveSpec/pkg/dbmanager/config.go
2026-01-02 16:19:33 +02:00

449 lines
14 KiB
Go

package dbmanager
import (
"fmt"
"time"
"github.com/bitechdev/ResolveSpec/pkg/config"
)
// DatabaseType represents the type of database
type DatabaseType string
const (
// DatabaseTypePostgreSQL represents PostgreSQL database
DatabaseTypePostgreSQL DatabaseType = "postgres"
// DatabaseTypeSQLite represents SQLite database
DatabaseTypeSQLite DatabaseType = "sqlite"
// DatabaseTypeMSSQL represents Microsoft SQL Server database
DatabaseTypeMSSQL DatabaseType = "mssql"
// DatabaseTypeMongoDB represents MongoDB database
DatabaseTypeMongoDB DatabaseType = "mongodb"
)
// ORMType represents the ORM to use for database operations
type ORMType string
const (
// ORMTypeBun represents Bun ORM
ORMTypeBun ORMType = "bun"
// ORMTypeGORM represents GORM
ORMTypeGORM ORMType = "gorm"
// ORMTypeNative represents native database/sql
ORMTypeNative ORMType = "native"
)
// ManagerConfig contains configuration for the database connection manager
type ManagerConfig struct {
// DefaultConnection is the name of the default connection to use
DefaultConnection string `mapstructure:"default_connection"`
// Connections is a map of connection name to connection configuration
Connections map[string]ConnectionConfig `mapstructure:"connections"`
// Global connection pool defaults
MaxOpenConns int `mapstructure:"max_open_conns"`
MaxIdleConns int `mapstructure:"max_idle_conns"`
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"`
// Retry policy
RetryAttempts int `mapstructure:"retry_attempts"`
RetryDelay time.Duration `mapstructure:"retry_delay"`
RetryMaxDelay time.Duration `mapstructure:"retry_max_delay"`
// Health checks
HealthCheckInterval time.Duration `mapstructure:"health_check_interval"`
EnableAutoReconnect bool `mapstructure:"enable_auto_reconnect"`
}
// ConnectionConfig defines configuration for a single database connection
type ConnectionConfig struct {
// Name is the unique name of this connection
Name string `mapstructure:"name"`
// Type is the database type (postgres, sqlite, mssql, mongodb)
Type DatabaseType `mapstructure:"type"`
// DSN is the complete Data Source Name / connection string
// If provided, this takes precedence over individual connection parameters
DSN string `mapstructure:"dsn"`
// Connection parameters (used if DSN is not provided)
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Database string `mapstructure:"database"`
// PostgreSQL/MSSQL specific
SSLMode string `mapstructure:"sslmode"` // disable, require, verify-ca, verify-full
Schema string `mapstructure:"schema"` // Default schema
// SQLite specific
FilePath string `mapstructure:"filepath"`
// MongoDB specific
AuthSource string `mapstructure:"auth_source"`
ReplicaSet string `mapstructure:"replica_set"`
ReadPreference string `mapstructure:"read_preference"` // primary, secondary, etc.
// Connection pool settings (overrides global defaults)
MaxOpenConns *int `mapstructure:"max_open_conns"`
MaxIdleConns *int `mapstructure:"max_idle_conns"`
ConnMaxLifetime *time.Duration `mapstructure:"conn_max_lifetime"`
ConnMaxIdleTime *time.Duration `mapstructure:"conn_max_idle_time"`
// Timeouts
ConnectTimeout time.Duration `mapstructure:"connect_timeout"`
QueryTimeout time.Duration `mapstructure:"query_timeout"`
// Features
EnableTracing bool `mapstructure:"enable_tracing"`
EnableMetrics bool `mapstructure:"enable_metrics"`
EnableLogging bool `mapstructure:"enable_logging"`
// DefaultORM specifies which ORM to use for the Database() method
// Options: "bun", "gorm", "native"
DefaultORM string `mapstructure:"default_orm"`
// Tags for organization and filtering
Tags map[string]string `mapstructure:"tags"`
}
// DefaultManagerConfig returns a ManagerConfig with sensible defaults
func DefaultManagerConfig() ManagerConfig {
return ManagerConfig{
DefaultConnection: "",
Connections: make(map[string]ConnectionConfig),
MaxOpenConns: 25,
MaxIdleConns: 5,
ConnMaxLifetime: 30 * time.Minute,
ConnMaxIdleTime: 5 * time.Minute,
RetryAttempts: 3,
RetryDelay: 1 * time.Second,
RetryMaxDelay: 10 * time.Second,
HealthCheckInterval: 30 * time.Second,
EnableAutoReconnect: true,
}
}
// ApplyDefaults applies default values to the manager configuration
func (c *ManagerConfig) ApplyDefaults() {
defaults := DefaultManagerConfig()
if c.MaxOpenConns == 0 {
c.MaxOpenConns = defaults.MaxOpenConns
}
if c.MaxIdleConns == 0 {
c.MaxIdleConns = defaults.MaxIdleConns
}
if c.ConnMaxLifetime == 0 {
c.ConnMaxLifetime = defaults.ConnMaxLifetime
}
if c.ConnMaxIdleTime == 0 {
c.ConnMaxIdleTime = defaults.ConnMaxIdleTime
}
if c.RetryAttempts == 0 {
c.RetryAttempts = defaults.RetryAttempts
}
if c.RetryDelay == 0 {
c.RetryDelay = defaults.RetryDelay
}
if c.RetryMaxDelay == 0 {
c.RetryMaxDelay = defaults.RetryMaxDelay
}
if c.HealthCheckInterval == 0 {
c.HealthCheckInterval = defaults.HealthCheckInterval
}
}
// Validate validates the manager configuration
func (c *ManagerConfig) Validate() error {
if len(c.Connections) == 0 {
return NewConfigurationError("connections", fmt.Errorf("at least one connection must be configured"))
}
if c.DefaultConnection != "" {
if _, ok := c.Connections[c.DefaultConnection]; !ok {
return NewConfigurationError("default_connection", fmt.Errorf("default connection '%s' not found in connections", c.DefaultConnection))
}
}
// Validate each connection
for name := range c.Connections {
conn := c.Connections[name]
if err := conn.Validate(); err != nil {
return fmt.Errorf("connection '%s': %w", name, err)
}
}
return nil
}
// ApplyDefaults applies default values and global settings to the connection configuration
func (cc *ConnectionConfig) ApplyDefaults(global *ManagerConfig) {
// Set name if not already set
if cc.Name == "" {
cc.Name = "unnamed"
}
// Apply global pool settings if not overridden
if cc.MaxOpenConns == nil && global != nil {
maxOpen := global.MaxOpenConns
cc.MaxOpenConns = &maxOpen
}
if cc.MaxIdleConns == nil && global != nil {
maxIdle := global.MaxIdleConns
cc.MaxIdleConns = &maxIdle
}
if cc.ConnMaxLifetime == nil && global != nil {
lifetime := global.ConnMaxLifetime
cc.ConnMaxLifetime = &lifetime
}
if cc.ConnMaxIdleTime == nil && global != nil {
idleTime := global.ConnMaxIdleTime
cc.ConnMaxIdleTime = &idleTime
}
// Default timeouts
if cc.ConnectTimeout == 0 {
cc.ConnectTimeout = 10 * time.Second
}
if cc.QueryTimeout == 0 {
cc.QueryTimeout = 30 * time.Second
}
// Default ORM
if cc.DefaultORM == "" {
cc.DefaultORM = string(ORMTypeBun)
}
// Default PostgreSQL port
if cc.Type == DatabaseTypePostgreSQL && cc.Port == 0 && cc.DSN == "" {
cc.Port = 5432
}
// Default MSSQL port
if cc.Type == DatabaseTypeMSSQL && cc.Port == 0 && cc.DSN == "" {
cc.Port = 1433
}
// Default MongoDB port
if cc.Type == DatabaseTypeMongoDB && cc.Port == 0 && cc.DSN == "" {
cc.Port = 27017
}
// Default MongoDB auth source
if cc.Type == DatabaseTypeMongoDB && cc.AuthSource == "" {
cc.AuthSource = "admin"
}
}
// Validate validates the connection configuration
func (cc *ConnectionConfig) Validate() error {
// Validate database type
switch cc.Type {
case DatabaseTypePostgreSQL, DatabaseTypeSQLite, DatabaseTypeMSSQL, DatabaseTypeMongoDB:
// Valid types
default:
return NewConfigurationError("type", fmt.Errorf("unsupported database type: %s", cc.Type))
}
// Validate that either DSN or connection parameters are provided
if cc.DSN == "" {
switch cc.Type {
case DatabaseTypePostgreSQL, DatabaseTypeMSSQL, DatabaseTypeMongoDB:
if cc.Host == "" {
return NewConfigurationError("host", fmt.Errorf("host is required when DSN is not provided"))
}
if cc.Database == "" {
return NewConfigurationError("database", fmt.Errorf("database is required when DSN is not provided"))
}
case DatabaseTypeSQLite:
if cc.FilePath == "" {
return NewConfigurationError("filepath", fmt.Errorf("filepath is required for SQLite when DSN is not provided"))
}
}
}
// Validate ORM type
if cc.DefaultORM != "" {
switch ORMType(cc.DefaultORM) {
case ORMTypeBun, ORMTypeGORM, ORMTypeNative:
// Valid ORM types
default:
return NewConfigurationError("default_orm", fmt.Errorf("unsupported ORM type: %s", cc.DefaultORM))
}
}
return nil
}
// BuildDSN builds a connection string from individual parameters
func (cc *ConnectionConfig) BuildDSN() (string, error) {
// If DSN is already provided, use it
if cc.DSN != "" {
return cc.DSN, nil
}
switch cc.Type {
case DatabaseTypePostgreSQL:
return cc.buildPostgresDSN(), nil
case DatabaseTypeSQLite:
return cc.buildSQLiteDSN(), nil
case DatabaseTypeMSSQL:
return cc.buildMSSQLDSN(), nil
case DatabaseTypeMongoDB:
return cc.buildMongoDSN(), nil
default:
return "", fmt.Errorf("cannot build DSN for database type: %s", cc.Type)
}
}
func (cc *ConnectionConfig) buildPostgresDSN() string {
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s",
cc.Host, cc.Port, cc.User, cc.Password, cc.Database)
if cc.SSLMode != "" {
dsn += fmt.Sprintf(" sslmode=%s", cc.SSLMode)
} else {
dsn += " sslmode=disable"
}
if cc.Schema != "" {
dsn += fmt.Sprintf(" search_path=%s", cc.Schema)
}
return dsn
}
func (cc *ConnectionConfig) buildSQLiteDSN() string {
if cc.FilePath != "" {
return cc.FilePath
}
return ":memory:"
}
func (cc *ConnectionConfig) buildMSSQLDSN() string {
// Format: sqlserver://username:password@host:port?database=dbname
dsn := fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s",
cc.User, cc.Password, cc.Host, cc.Port, cc.Database)
if cc.Schema != "" {
dsn += fmt.Sprintf("&schema=%s", cc.Schema)
}
return dsn
}
func (cc *ConnectionConfig) buildMongoDSN() string {
// Format: mongodb://username:password@host:port/database?authSource=admin
var dsn string
if cc.User != "" && cc.Password != "" {
dsn = fmt.Sprintf("mongodb://%s:%s@%s:%d/%s",
cc.User, cc.Password, cc.Host, cc.Port, cc.Database)
} else {
dsn = fmt.Sprintf("mongodb://%s:%d/%s", cc.Host, cc.Port, cc.Database)
}
params := ""
if cc.AuthSource != "" {
params += fmt.Sprintf("authSource=%s", cc.AuthSource)
}
if cc.ReplicaSet != "" {
if params != "" {
params += "&"
}
params += fmt.Sprintf("replicaSet=%s", cc.ReplicaSet)
}
if cc.ReadPreference != "" {
if params != "" {
params += "&"
}
params += fmt.Sprintf("readPreference=%s", cc.ReadPreference)
}
if params != "" {
dsn += "?" + params
}
return dsn
}
// FromConfig converts config.DBManagerConfig to internal ManagerConfig
func FromConfig(cfg config.DBManagerConfig) ManagerConfig {
mgr := ManagerConfig{
DefaultConnection: cfg.DefaultConnection,
Connections: make(map[string]ConnectionConfig),
MaxOpenConns: cfg.MaxOpenConns,
MaxIdleConns: cfg.MaxIdleConns,
ConnMaxLifetime: cfg.ConnMaxLifetime,
ConnMaxIdleTime: cfg.ConnMaxIdleTime,
RetryAttempts: cfg.RetryAttempts,
RetryDelay: cfg.RetryDelay,
RetryMaxDelay: cfg.RetryMaxDelay,
HealthCheckInterval: cfg.HealthCheckInterval,
EnableAutoReconnect: cfg.EnableAutoReconnect,
}
// Convert connections
for name := range cfg.Connections {
connCfg := cfg.Connections[name]
mgr.Connections[name] = ConnectionConfig{
Name: connCfg.Name,
Type: DatabaseType(connCfg.Type),
DSN: connCfg.DSN,
Host: connCfg.Host,
Port: connCfg.Port,
User: connCfg.User,
Password: connCfg.Password,
Database: connCfg.Database,
SSLMode: connCfg.SSLMode,
Schema: connCfg.Schema,
FilePath: connCfg.FilePath,
AuthSource: connCfg.AuthSource,
ReplicaSet: connCfg.ReplicaSet,
ReadPreference: connCfg.ReadPreference,
MaxOpenConns: connCfg.MaxOpenConns,
MaxIdleConns: connCfg.MaxIdleConns,
ConnMaxLifetime: connCfg.ConnMaxLifetime,
ConnMaxIdleTime: connCfg.ConnMaxIdleTime,
ConnectTimeout: connCfg.ConnectTimeout,
QueryTimeout: connCfg.QueryTimeout,
EnableTracing: connCfg.EnableTracing,
EnableMetrics: connCfg.EnableMetrics,
EnableLogging: connCfg.EnableLogging,
DefaultORM: connCfg.DefaultORM,
Tags: connCfg.Tags,
}
}
return mgr
}
// Getter methods to implement providers.ConnectionConfig interface
func (cc *ConnectionConfig) GetName() string { return cc.Name }
func (cc *ConnectionConfig) GetType() string { return string(cc.Type) }
func (cc *ConnectionConfig) GetHost() string { return cc.Host }
func (cc *ConnectionConfig) GetPort() int { return cc.Port }
func (cc *ConnectionConfig) GetUser() string { return cc.User }
func (cc *ConnectionConfig) GetPassword() string { return cc.Password }
func (cc *ConnectionConfig) GetDatabase() string { return cc.Database }
func (cc *ConnectionConfig) GetFilePath() string { return cc.FilePath }
func (cc *ConnectionConfig) GetConnectTimeout() time.Duration { return cc.ConnectTimeout }
func (cc *ConnectionConfig) GetEnableLogging() bool { return cc.EnableLogging }
func (cc *ConnectionConfig) GetMaxOpenConns() *int { return cc.MaxOpenConns }
func (cc *ConnectionConfig) GetMaxIdleConns() *int { return cc.MaxIdleConns }
func (cc *ConnectionConfig) GetConnMaxLifetime() *time.Duration { return cc.ConnMaxLifetime }
func (cc *ConnectionConfig) GetConnMaxIdleTime() *time.Duration { return cc.ConnMaxIdleTime }
func (cc *ConnectionConfig) GetQueryTimeout() time.Duration { return cc.QueryTimeout }
func (cc *ConnectionConfig) GetEnableMetrics() bool { return cc.EnableMetrics }
func (cc *ConnectionConfig) GetReadPreference() string { return cc.ReadPreference }