Add PostgreSQL support to database package alongside existing MySQL support. Both databases share common infrastructure (pool management, metrics, transactions) while using database-specific connectors. database/ changes: - Add PostgresConfig struct and PostgreSQL connector using pgx/stdlib - Change MySQL config from DBConfig to *MySQLConfig (pointer) - Add Config.Validate() to prevent multiple database configs - Add PostgreSQL connector with secure config building (no password in DSN) - Add field validation and secure defaults (SSLMode="prefer") - Support legacy flat PostgreSQL config format for backward compatibility - Add tests for PostgreSQL configs and validation New database/pgdb/ package: - Native pgx connection pool support (*pgxpool.Pool) - OpenPool() and OpenPoolWithConfigFile() APIs - CreatePoolConfig() for secure config conversion - PoolOptions for fine-grained pool control - Full test coverage and documentation Security: - Passwords never exposed in DSN strings - Set passwords separately in pgx config objects - Validate all configuration before connection Architecture: - Shared code in database/ for both MySQL and PostgreSQL (sql.DB) - database/pgdb/ for PostgreSQL-specific native pool support
65 lines
1.6 KiB
Go
65 lines
1.6 KiB
Go
package pgdb
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"go.ntppool.org/common/database"
|
|
)
|
|
|
|
// CreatePoolConfig converts database.PostgresConfig to pgxpool.Config
|
|
// This is the secure way to create a config without exposing passwords in DSN strings
|
|
func CreatePoolConfig(cfg *database.PostgresConfig) (*pgxpool.Config, error) {
|
|
// Validate required fields
|
|
if cfg.Host == "" {
|
|
return nil, fmt.Errorf("postgres: host is required")
|
|
}
|
|
if cfg.User == "" {
|
|
return nil, fmt.Errorf("postgres: user is required")
|
|
}
|
|
if cfg.Name == "" {
|
|
return nil, fmt.Errorf("postgres: database name is required")
|
|
}
|
|
|
|
// Validate SSLMode
|
|
validSSLModes := map[string]bool{
|
|
"disable": true, "allow": true, "prefer": true,
|
|
"require": true, "verify-ca": true, "verify-full": true,
|
|
}
|
|
if cfg.SSLMode != "" && !validSSLModes[cfg.SSLMode] {
|
|
return nil, fmt.Errorf("postgres: invalid sslmode: %s", cfg.SSLMode)
|
|
}
|
|
|
|
// Set defaults
|
|
host := cfg.Host
|
|
if host == "" {
|
|
host = "localhost"
|
|
}
|
|
|
|
port := cfg.Port
|
|
if port == 0 {
|
|
port = 5432
|
|
}
|
|
|
|
sslmode := cfg.SSLMode
|
|
if sslmode == "" {
|
|
sslmode = "prefer"
|
|
}
|
|
|
|
// Build connection string WITHOUT password
|
|
// We'll set the password separately in the config
|
|
connString := fmt.Sprintf("host=%s port=%d user=%s dbname=%s sslmode=%s",
|
|
host, port, cfg.User, cfg.Name, sslmode)
|
|
|
|
// Parse the connection string
|
|
poolConfig, err := pgxpool.ParseConfig(connString)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("postgres: failed to parse connection config: %w", err)
|
|
}
|
|
|
|
// Set password separately (security: never put password in the connection string)
|
|
poolConfig.ConnConfig.Password = cfg.Pass
|
|
|
|
return poolConfig, nil
|
|
}
|