mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-01-05 03:14:25 +00:00
185 lines
4.2 KiB
Go
185 lines
4.2 KiB
Go
package providers
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
_ "github.com/microsoft/go-mssqldb" // MSSQL driver
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
|
)
|
|
|
|
// MSSQLProvider implements Provider for Microsoft SQL Server databases
|
|
type MSSQLProvider struct {
|
|
db *sql.DB
|
|
config ConnectionConfig
|
|
}
|
|
|
|
// NewMSSQLProvider creates a new MSSQL provider
|
|
func NewMSSQLProvider() *MSSQLProvider {
|
|
return &MSSQLProvider{}
|
|
}
|
|
|
|
// Connect establishes a MSSQL connection
|
|
func (p *MSSQLProvider) Connect(ctx context.Context, cfg ConnectionConfig) error {
|
|
// Build DSN
|
|
dsn, err := cfg.BuildDSN()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build DSN: %w", err)
|
|
}
|
|
|
|
// Connect with retry logic
|
|
var db *sql.DB
|
|
var lastErr error
|
|
|
|
retryAttempts := 3 // Default retry attempts
|
|
retryDelay := 1 * time.Second
|
|
|
|
for attempt := 0; attempt < retryAttempts; attempt++ {
|
|
if attempt > 0 {
|
|
delay := calculateBackoff(attempt, retryDelay, 10*time.Second)
|
|
if cfg.GetEnableLogging() {
|
|
logger.Info("Retrying MSSQL connection: attempt=%d/%d, delay=%v", attempt+1, retryAttempts, delay)
|
|
}
|
|
|
|
select {
|
|
case <-time.After(delay):
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
|
|
// Open database connection
|
|
db, err = sql.Open("sqlserver", dsn)
|
|
if err != nil {
|
|
lastErr = err
|
|
if cfg.GetEnableLogging() {
|
|
logger.Warn("Failed to open MSSQL connection", "error", err)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Test the connection with context timeout
|
|
connectCtx, cancel := context.WithTimeout(ctx, cfg.GetConnectTimeout())
|
|
err = db.PingContext(connectCtx)
|
|
cancel()
|
|
|
|
if err != nil {
|
|
lastErr = err
|
|
db.Close()
|
|
if cfg.GetEnableLogging() {
|
|
logger.Warn("Failed to ping MSSQL database", "error", err)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Connection successful
|
|
break
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect after %d attempts: %w", retryAttempts, lastErr)
|
|
}
|
|
|
|
// Configure connection pool
|
|
if cfg.GetMaxOpenConns() != nil {
|
|
db.SetMaxOpenConns(*cfg.GetMaxOpenConns())
|
|
}
|
|
if cfg.GetMaxIdleConns() != nil {
|
|
db.SetMaxIdleConns(*cfg.GetMaxIdleConns())
|
|
}
|
|
if cfg.GetConnMaxLifetime() != nil {
|
|
db.SetConnMaxLifetime(*cfg.GetConnMaxLifetime())
|
|
}
|
|
if cfg.GetConnMaxIdleTime() != nil {
|
|
db.SetConnMaxIdleTime(*cfg.GetConnMaxIdleTime())
|
|
}
|
|
|
|
p.db = db
|
|
p.config = cfg
|
|
|
|
if cfg.GetEnableLogging() {
|
|
logger.Info("MSSQL connection established: name=%s, host=%s, database=%s", cfg.GetName(), cfg.GetHost(), cfg.GetDatabase())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes the MSSQL connection
|
|
func (p *MSSQLProvider) Close() error {
|
|
if p.db == nil {
|
|
return nil
|
|
}
|
|
|
|
err := p.db.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to close MSSQL connection: %w", err)
|
|
}
|
|
|
|
if p.config.GetEnableLogging() {
|
|
logger.Info("MSSQL connection closed: name=%s", p.config.GetName())
|
|
}
|
|
|
|
p.db = nil
|
|
return nil
|
|
}
|
|
|
|
// HealthCheck verifies the MSSQL connection is alive
|
|
func (p *MSSQLProvider) HealthCheck(ctx context.Context) error {
|
|
if p.db == nil {
|
|
return fmt.Errorf("database connection is nil")
|
|
}
|
|
|
|
// Use a short timeout for health checks
|
|
healthCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
|
|
if err := p.db.PingContext(healthCtx); err != nil {
|
|
return fmt.Errorf("health check failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetNative returns the native *sql.DB connection
|
|
func (p *MSSQLProvider) GetNative() (*sql.DB, error) {
|
|
if p.db == nil {
|
|
return nil, fmt.Errorf("database connection is not initialized")
|
|
}
|
|
return p.db, nil
|
|
}
|
|
|
|
// GetMongo returns an error for MSSQL (not a MongoDB connection)
|
|
func (p *MSSQLProvider) GetMongo() (*mongo.Client, error) {
|
|
return nil, ErrNotMongoDB
|
|
}
|
|
|
|
// Stats returns connection pool statistics
|
|
func (p *MSSQLProvider) Stats() *ConnectionStats {
|
|
if p.db == nil {
|
|
return &ConnectionStats{
|
|
Name: p.config.GetName(),
|
|
Type: "mssql",
|
|
Connected: false,
|
|
}
|
|
}
|
|
|
|
stats := p.db.Stats()
|
|
|
|
return &ConnectionStats{
|
|
Name: p.config.GetName(),
|
|
Type: "mssql",
|
|
Connected: true,
|
|
OpenConnections: stats.OpenConnections,
|
|
InUse: stats.InUse,
|
|
Idle: stats.Idle,
|
|
WaitCount: stats.WaitCount,
|
|
WaitDuration: stats.WaitDuration,
|
|
MaxIdleClosed: stats.MaxIdleClosed,
|
|
MaxLifetimeClosed: stats.MaxLifetimeClosed,
|
|
}
|
|
}
|