feat(database): add PostgreSQL support with native pgx pool
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
This commit is contained in:
64
database/pgdb/config.go
Normal file
64
database/pgdb/config.go
Normal file
@@ -0,0 +1,64 @@
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user