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:
2025-09-27 16:55:54 -07:00
parent 4767caf7b8
commit 45308cd4bf
9 changed files with 832 additions and 43 deletions

View File

@@ -1,6 +1,7 @@
package database
import (
"fmt"
"os"
"time"
@@ -9,15 +10,67 @@ import (
// Config represents the database configuration structure
type Config struct {
MySQL DBConfig `yaml:"mysql"`
// MySQL configuration (use this OR Postgres, not both)
MySQL *MySQLConfig `yaml:"mysql,omitempty"`
// Postgres configuration (use this OR MySQL, not both)
Postgres *PostgresConfig `yaml:"postgres,omitempty"`
// Legacy flat PostgreSQL format (deprecated, for backward compatibility only)
// If neither MySQL nor Postgres is set, these fields will be used for PostgreSQL
User string `yaml:"user,omitempty"`
Pass string `yaml:"pass,omitempty"`
Host string `yaml:"host,omitempty"`
Port uint16 `yaml:"port,omitempty"`
Name string `yaml:"name,omitempty"`
SSLMode string `yaml:"sslmode,omitempty"`
}
// DBConfig represents the MySQL database configuration
type DBConfig struct {
DSN string `default:"" flag:"dsn" usage:"Database DSN"`
User string `default:"" flag:"user"`
Pass string `default:"" flag:"pass"`
DBName string // Optional database name override
// MySQLConfig represents the MySQL database configuration
type MySQLConfig struct {
DSN string `yaml:"dsn" default:"" flag:"dsn" usage:"Database DSN"`
User string `yaml:"user" default:"" flag:"user"`
Pass string `yaml:"pass" default:"" flag:"pass"`
DBName string `yaml:"name,omitempty"` // Optional database name override
}
// PostgresConfig represents the PostgreSQL database configuration
type PostgresConfig struct {
User string `yaml:"user"`
Pass string `yaml:"pass"`
Host string `yaml:"host"`
Port uint16 `yaml:"port"`
Name string `yaml:"name"`
SSLMode string `yaml:"sslmode"`
}
// DBConfig is a legacy alias for MySQLConfig
type DBConfig = MySQLConfig
// Validate ensures the configuration is valid and unambiguous
func (c *Config) Validate() error {
hasMySQL := c.MySQL != nil
hasPostgres := c.Postgres != nil
hasLegacy := c.User != "" || c.Host != "" || c.Port != 0 || c.Name != ""
count := 0
if hasMySQL {
count++
}
if hasPostgres {
count++
}
if hasLegacy {
count++
}
if count == 0 {
return fmt.Errorf("no database configuration provided")
}
if count > 1 {
return fmt.Errorf("multiple database configurations provided (only one allowed)")
}
return nil
}
// ConfigOptions allows customization of database opening behavior