mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-01-17 08:24:26 +00:00
feat(dbmanager): ✨ Database connection Manager
This commit is contained in:
448
pkg/dbmanager/config.go
Normal file
448
pkg/dbmanager/config.go
Normal file
@@ -0,0 +1,448 @@
|
||||
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 }
|
||||
Reference in New Issue
Block a user