postgresenv

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 8, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package postgresenv provides PostgreSQL environment management for testing. It offers flexible connection management with support for containers, connection pooling, and integration with the testenv framework.

The package provides:

  • Connector type for managing PostgreSQL connections
  • Environment type for high-level database environment management
  • Support for multiple reuse strategies (new database, new schema, raw connection)
  • Integration with Docker containers via testcontainers
  • Standard library compatibility via pgx stdlib wrapper

Example Usage:

// Simple connector
connector := postgresenv.NewConnector(
	postgresenv.WithHost("localhost"),
	postgresenv.WithPort(5432),
	postgresenv.WithDatabase("testdb"),
)

// Connect to database
pool, err := connector.Connect(ctx, nil)
if err != nil {
	return err
}
defer pool.Close()

// Use pool for database operations...

Package postgresenv provides PostgreSQL environment management for testing. It offers flexible connection management with support for containers, connection pooling, and integration with the testenv framework.

The package provides:

  • Connector type for managing PostgreSQL connections
  • Environment type for high-level database environment management
  • Support for multiple reuse strategies (new database, new schema, raw connection)
  • Integration with Docker containers via testcontainers
  • Standard library compatibility via pgx stdlib wrapper

Example Usage:

// Simple connector
connector := postgresenv.NewConnector(
	postgresenv.WithHost("localhost"),
	postgresenv.WithPort(5432),
	postgresenv.WithDatabase("testdb"),
)

// Connect to database
pool, err := connector.Connect(ctx, nil)
if err != nil {
	return err
}
defer pool.Close()

// Use pool for database operations...

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BeforeConnectHook

type BeforeConnectHook func(ctx context.Context, cfg *pgxpool.Config) error

BeforeConnectHook is a function that is called before establishing a database connection. It allows modification of the connection configuration before the connection is created. This is useful for scenarios like:

  • Starting a container before the first connection
  • Modifying connection parameters based on runtime conditions
  • Implementing custom connection initialization logic

Parameters:

  • ctx: Context for cancellation and timeout
  • cfg: Connection configuration that can be modified

Returns:

  • error: Non-nil to prevent the connection from being established

Example:

hook := func(ctx context.Context, cfg *pgxpool.Config) error {
	// Start container on first connection
	return container.Start(ctx)
}

type CloseHook

type CloseHook func() error

CloseHook is a function that is called when the connector is closed. It allows cleanup of resources associated with the connector. This is useful for scenarios like:

  • Stopping containers when the connector is no longer needed
  • Cleaning up external resources
  • Releasing allocated resources

Returns:

  • error: Non-nil if cleanup fails

Example:

hook := func() error {
	// Stop container when connector closes
	return container.Stop()
}

type Connector

type Connector struct {
	// contains filtered or unexported fields
}

Connector manages database connections to a PostgreSQL instance. It provides a flexible connection management system with support for:

  • Connection pooling via pgx
  • Hook-based lifecycle management
  • Service pool sharing across environments
  • Configuration merging and overrides

Connector is designed to work with various PostgreSQL deployment scenarios:

  • Local PostgreSQL instances
  • Docker containers
  • Cloud databases
  • Test environments

Thread Safety: Connector is safe for concurrent use. The environmentsServicePools map is protected and can be accessed from multiple goroutines.

Example:

// Create connector with container lifecycle
cnt := containers.NewContainer()
connector := NewConnector(cnt.ConnectorOptions()...)

// Connect to database
pool, err := connector.Connect(ctx, nil)
if err != nil {
	return err
}
defer pool.Close()

// Use connection pool...

func NewConnector

func NewConnector(opts ...ConnectorOption) *Connector

NewConnector creates a new PostgreSQL connector with the specified options. If no options are provided, the connector uses sensible defaults suitable for local development and testing.

Default values:

  • Host: "0.0.0.0"
  • Port: 5432
  • Database: "default_database"
  • User: "default_user"
  • Password: "default_password"
  • Schema: "" (empty, uses database default)

Options are applied in order, allowing later options to override earlier ones.

Example:

// Create connector with custom settings
connector := NewConnector(
	WithHost("localhost"),
	WithPort(5433),
	WithDatabase("testdb"),
	WithUser("testuser"),
	WithPassword("testpass"),
)

// Create connector with container lifecycle
cnt := containers.NewContainer()
connector := NewConnector(cnt.ConnectorOptions()...)

// Create connector with hooks
connector := NewConnector(
	WithBeforeConnectHook(func(ctx context.Context, cfg *pgxpool.Config) error {
		fmt.Println("Connecting to database...")
		return nil
	}),
	WithCloseHook(func() error {
		fmt.Println("Connector closed")
		return nil
	}),
)

func (*Connector) Close

func (c *Connector) Close() error

Close closes all service pools and calls the close hook. This method cleans up all resources associated with the connector.

Cleanup Process:

  1. Closes all cached service pools
  2. Calls closeHook (if set)

Returns:

  • error: Non-nil if cleanup fails

After Close is called, the connector should not be used to create new connections.

Example:

// Create connector with container
cnt := containers.NewContainer()
connector := NewConnector(cnt.ConnectorOptions()...)

// Use connector...
pool, _ := connector.Connect(ctx, nil)
defer pool.Close()

// Clean up when done
defer connector.Close() // This will also stop the container

func (*Connector) Config

func (c *Connector) Config() (*pgxpool.Config, error)

Config returns the base pgxpool configuration for the connector. This method builds a connection configuration from the connector's settings.

The configuration includes:

  • Connection parameters (host, port, database, user, password)
  • Schema (search_path) if set
  • TLS disabled (suitable for local/test environments)

Returns:

  • *pgxpool.Config: Configuration ready to use or modify
  • error: Non-nil if configuration parsing fails

Example:

// Get base configuration
cfg, err := connector.Config()
if err != nil {
	return err
}

// Modify configuration
cfg.MaxConns = 10
cfg.ConnConfig.TLSConfig = &tls.Config{...}

// Use configuration
pool, err := pgxpool.NewWithConfig(ctx, cfg)

func (*Connector) Connect

func (c *Connector) Connect(ctx context.Context, cfg *pgxpool.Config) (*pgxpool.Pool, error)

Connect establishes a connection pool to the PostgreSQL database. This method creates a new connection pool using the connector's configuration.

Connection Process:

  1. Builds base configuration from connector settings
  2. Merges with provided configuration (if any)
  3. Calls beforeConnectHook (if set)
  4. Creates pgx connection pool

Parameters:

  • ctx: Context for cancellation and timeout
  • cfg: Optional configuration to merge with base config (can be nil)

Returns:

  • *pgxpool.Pool: Connection pool ready to use
  • error: Non-nil if connection fails

The cfg parameter allows overriding specific settings:

  • MaxConns: Maximum number of connections in the pool
  • Database: Database name (overrides connector's database)
  • Host/Port: Connection endpoint (overrides connector's settings)
  • RuntimeParams: PostgreSQL runtime parameters

Example:

// Connect with default settings
pool, err := connector.Connect(ctx, nil)
if err != nil {
	return err
}
defer pool.Close()

// Connect with custom pool size
pool, err := connector.Connect(ctx, &pgxpool.Config{
	MaxConns: 10,
})

// Connect to different database
pool, err := connector.Connect(ctx, &pgxpool.Config{
	ConnConfig: &pgx.ConnConfig{
		Config: pgconn.Config{
			Database: "other_db",
		},
	},
})

type ConnectorOption

type ConnectorOption func(*Connector)

ConnectorOption is a function that configures a Connector. Options can be passed to NewConnector to customize the connector's behavior.

func WithBeforeConnectHook

func WithBeforeConnectHook(hook BeforeConnectHook) ConnectorOption

WithBeforeConnectHook sets a callback function that is invoked before establishing a database connection. This allows for custom initialization logic or resource setup before the connection is created.

The hook is called every time Connect() is invoked, allowing for dynamic behavior based on runtime conditions.

Example:

// Start container before first connection
cnt := containers.NewContainer()
connector := NewConnector(
	WithBeforeConnectHook(cnt.Launch),
	WithCloseHook(cnt.Close),
)

func WithCloseHook

func WithCloseHook(hook CloseHook) ConnectorOption

WithCloseHook sets a callback function that is invoked when the connector is closed. This allows for cleanup of resources associated with the connector.

The hook is called when Close() is invoked, after all service pools are closed.

Example:

// Stop container when connector closes
cnt := containers.NewContainer()
connector := NewConnector(
	WithBeforeConnectHook(cnt.Launch),
	WithCloseHook(cnt.Close),
)

func WithDatabase

func WithDatabase(databaseName string) ConnectorOption

WithDatabase sets the database name to connect to.

Default: "default_database"

Example:

connector := NewConnector(
	WithDatabase("myapp_test"),
)

func WithHost

func WithHost(host string) ConnectorOption

WithHost sets the PostgreSQL server hostname or IP address.

Default: "0.0.0.0"

Example:

connector := NewConnector(
	WithHost("localhost"),
	WithPort(5432),
)

func WithPassword

func WithPassword(password string) ConnectorOption

WithPassword sets the PostgreSQL password for authentication.

Default: "default_password"

Example:

connector := NewConnector(
	WithUser("admin"),
	WithPassword("secret"),
)

func WithPort

func WithPort(port uint16) ConnectorOption

WithPort sets the PostgreSQL server port number.

Default: 5432

Example:

connector := NewConnector(
	WithPort(5433), // Non-standard port
)

func WithSchema

func WithSchema(schema string) ConnectorOption

WithSchema sets the default schema to use for connections. When set, this adds "search_path=<schema>" to the connection string.

Default: "" (no schema set, uses database default)

Example:

connector := NewConnector(
	WithSchema("myschema"),
)

func WithUser

func WithUser(user string) ConnectorOption

WithUser sets the PostgreSQL username for authentication.

Default: "default_user"

Example:

connector := NewConnector(
	WithUser("admin"),
	WithPassword("secret"),
)

type Environment

type Environment struct {
	// contains filtered or unexported fields
}

Environment manages PostgreSQL connection pools with flexible reuse strategies. It provides a high-level interface for creating database connections with automatic resource management and isolation.

Environment supports different reuse strategies to balance isolation and performance:

  • No strategy (default): Direct connection to PostgreSQL, no automatic cleanup
  • CreateNewDB: Full isolation with a new database per connection
  • CreateNewSchema: Good isolation with a new schema per connection

Environment integrates with the reuse package to efficiently manage connectors and resources across multiple connections and tests.

Thread Safety: Environment is safe for concurrent use. Multiple goroutines can call Connect concurrently, and each will receive an independent Pool.

Example:

// Create environment with container
cnt := containers.NewContainer()
env := postgresenv.New(
	postgresenv.WithConnectorReuseOptions(func() (*Connector, error) {
		return NewConnector(cnt.ConnectorOptions()...), nil
	}),
	postgresenv.WithReuseStrategy(&ReuseStrategyCreateNewDB{}),
	postgresenv.WithMaxConnsPerConnect(10),
)

// Connect
pool, err := env.Connect(ctx)
if err != nil {
	return err
}
defer pool.Close()

// Use pool...

func New

func New(opts ...EnvironmentOption) *Environment

New creates a new PostgreSQL environment with the specified options. The environment manages connection pooling and resource reuse according to the configured strategy.

Default Configuration:

  • Reuse Strategy: None (direct connection)
  • Max Conns Per Connect: 1
  • Connector: Default connector with standard PostgreSQL settings

Options are applied in order, allowing later options to override earlier ones.

Example:

// Simple environment
env := postgresenv.New()

// Environment with container
cnt := containers.NewContainer()
env := postgresenv.New(
	postgresenv.WithConnectorReuseOptions(func() (*Connector, error) {
		return NewConnector(cnt.ConnectorOptions()...), nil
	}),
)

// Environment with all options
env := postgresenv.New(
	postgresenv.WithReuseStrategy(&ReuseStrategyCreateNewDB{}),
	postgresenv.WithConnectorReuseOptions(connectorFactory),
	postgresenv.WithMaxConnsPerConnect(10),
)

func (*Environment) Connect

func (e *Environment) Connect(ctx context.Context) (*Pool, error)

Connect creates a new database connection pool according to the configured reuse strategy.

Connection Process:

  1. Acquires a connector from the reuse pool
  2. Applies the configured reuse strategy: - No strategy: Connects directly to PostgreSQL - CreateNewDB: Creates a new database and connects to it - CreateNewSchema: Creates a new schema and connects to it
  3. Returns a Pool ready for database operations

The returned Pool must be closed when no longer needed. Closing the pool automatically cleans up any resources created by the reuse strategy.

Parameters:

  • ctx: Context for cancellation and timeout

Returns:

  • *Pool: Connection pool ready for database operations
  • error: Non-nil if connection fails

Example:

pool, err := env.Connect(ctx)
if err != nil {
	return err
}
defer pool.Close()

// Use pool.Pool for database operations
rows, err := pool.Pool.Query(ctx, "SELECT * FROM users")

type EnvironmentOption

type EnvironmentOption func(*Environment)

EnvironmentOption is a function that configures an Environment. Options can be passed to New to customize the environment's behavior.

func WithConnectorReuseOptions

func WithConnectorReuseOptions(
	createFunc func() (*Connector, error),
	opts ...reuse.Option[*Connector],
) EnvironmentOption

func WithMaxConnsPerConnect

func WithMaxConnsPerConnect(maxConnPerConnect int32) EnvironmentOption

WithMaxConnsPerConnect sets the maximum number of connections in each pool created by the environment.

Default: 1

For most test scenarios, the default of 1 is appropriate. Increase this value if you need to test connection pooling behavior or concurrent database access within a single test.

Example:

env := postgresenv.New(
	postgresenv.WithMaxConnsPerConnect(10),
)

func WithReuseStrategy

func WithReuseStrategy(strategy ReuseStrategy) EnvironmentOption

WithReuseStrategy sets the strategy for reusing database resources. Different strategies provide different levels of isolation and performance.

If not set, the environment connects directly to PostgreSQL without creating additional resources (no automatic database/schema creation).

Example:

// Create new database per connection
env := postgresenv.New(
	postgresenv.WithReuseStrategy(&ReuseStrategyCreateNewDB{}),
)

// Create new schema per connection
env := postgresenv.New(
	postgresenv.WithReuseStrategy(&ReuseStrategyCreateNewSchema{
		DB: "mydb",
	}),
)

type Pool

type Pool struct {
	// Pool is the underlying pgx connection pool ready for database operations.
	Pool *pgxpool.Pool
	// contains filtered or unexported fields
}

Pool represents a database connection pool with lifecycle management. It wraps a pgxpool.Pool with automatic cleanup and connector reuse tracking.

Pool instances are created by Environment.Connect and should be closed using the Close method when no longer needed. The Close method handles both pool cleanup and connector lifecycle management.

Thread Safety: Pool is safe for concurrent use. The underlying pgxpool.Pool handles connection pooling and concurrent access safely.

Example:

env := postgresenv.New(
	postgresenv.WithConnectorReuseOptions(connectorFactory),
)

pool, err := env.Connect(ctx)
if err != nil {
	return err
}
defer pool.Close()

// Use pool.Pool for database operations
rows, err := pool.Pool.Query(ctx, "SELECT * FROM users")

func (*Pool) Close

func (p *Pool) Close()

Close closes the connection pool and releases all associated resources. This method:

  1. Closes the underlying pgxpool.Pool
  2. Calls the cleanup function (if set) to remove temporary resources
  3. Exits the connector reuse tracking

Close should be called when the pool is no longer needed. After Close is called, the pool should not be used for any database operations.

Example:

pool, err := env.Connect(ctx)
if err != nil {
	return err
}
defer pool.Close()

// Use pool...

type ReuseStrategy

type ReuseStrategy interface {
	// contains filtered or unexported methods
}

ReuseStrategy defines the strategy for reusing database resources across multiple connections. Different strategies provide different levels of isolation and performance characteristics.

Implementations:

  • ReuseStrategyCreateNewDB: Creates a new database for each connection
  • ReuseStrategyCreateNewSchema: Creates a new schema in a shared database
  • nil (default): Uses raw PostgreSQL connection without additional isolation

type ReuseStrategyCreateNewDB

type ReuseStrategyCreateNewDB struct {
	// NewDBName is a function that generates a unique database name.
	// If nil, a default name generator is used that creates names like
	// "public123456789" using random numbers.
	//
	// The function should return a unique name each time it's called.
	// Names are automatically converted to lowercase.
	NewDBName func() string
}

ReuseStrategyCreateNewDB creates a new database for each connection. This provides complete isolation between tests but may be slower due to database creation overhead.

Each time Connect is called, a new database is created with a unique name. When the pool is closed, the database is automatically dropped.

Use this strategy when:

  • You need complete isolation between tests
  • Tests modify database-level settings
  • Tests create/drop tables, indexes, or other database objects

Example:

env := postgresenv.New(
	postgresenv.WithReuseStrategy(&postgresenv.ReuseStrategyCreateNewDB{
		NewDBName: func() string {
			return "testdb_" + uuid.New().String()
		},
	}),
	postgresenv.WithConnectorReuseOptions(connectorFactory),
)

type ReuseStrategyCreateNewSchema

type ReuseStrategyCreateNewSchema struct {
	// DB is the name of the database in which to create schemas.
	// This database must already exist.
	DB string

	// NewSchemaName is a function that generates a unique schema name.
	// If nil, a default name generator is used that creates names like
	// "public123456789" using random numbers.
	//
	// The function should return a unique name each time it's called.
	// Names are automatically converted to lowercase.
	NewSchemaName func() string
}

ReuseStrategyCreateNewSchema creates a new schema in an existing database for each connection. This provides good isolation with better performance than creating new databases.

Each time Connect is called, a new schema is created within the specified database. When the pool is closed, the schema is automatically dropped.

Use this strategy when:

  • You want good isolation but better performance than creating databases
  • Tests only create/drop tables and other schema-level objects
  • Multiple tests need to share the same database but not the same schema

Example:

env := postgresenv.New(
	postgresenv.WithReuseStrategy(&postgresenv.ReuseStrategyCreateNewSchema{
		DB: "mydb", // Use existing database
		NewSchemaName: func() string {
			return "testschema_" + uuid.New().String()
		},
	}),
	postgresenv.WithConnectorReuseOptions(connectorFactory),
)

type StdlibEnvironment

type StdlibEnvironment struct {
	// contains filtered or unexported fields
}

StdlibEnvironment wraps a pgx-based Environment to provide standard library database/sql compatibility.

This allows projects that use the standard *sql.DB interface to work seamlessly with the testenv framework. The wrapper handles the conversion between pgx connection pools and standard library connections while maintaining all the benefits of the testenv environment management.

Key Features:

  • Provides *sql.DB instead of *pgxpool.Pool
  • Maintains all environment benefits (reuse strategies, cleanup, etc.)
  • Automatic resource cleanup when DB is closed
  • Full compatibility with database/sql ecosystem

Example:

// Create base environment with container
cnt := containers.NewContainer()
env := postgresenv.New(
	postgresenv.WithConnectorReuseOptions(func() (*Connector, error) {
		return NewConnector(cnt.ConnectorOptions()...), nil
	}),
	postgresenv.WithReuseStrategy(&ReuseStrategyCreateNewDB{}),
)

// Wrap with stdlib compatibility
stdlibEnv := postgresenv.NewStdlib(env)

// Connect using standard library
db, err := stdlibEnv.Connect(ctx)
if err != nil {
	return err
}
defer db.Close()

// Use standard *sql.DB
rows, err := db.QueryContext(ctx, "SELECT * FROM users")

func NewStdlib

func NewStdlib(
	env *Environment,
	opts ...stdlib.OptionOpenDB,
) *StdlibEnvironment

NewStdlib creates a new stdlib-compatible environment wrapper. This is the primary constructor for creating standard library compatible database environments.

Parameters:

  • env: The underlying pgx-based environment to wrap
  • opts: Optional stdlib configuration options (can be used to customize connector behavior, such as setting connection timeouts or other parameters)

Returns:

  • *StdlibEnvironment: A new environment that provides *sql.DB connections

Example:

// Create base environment
cnt := containers.NewContainer()
env := postgresenv.New(
	postgresenv.WithConnectorReuseOptions(func() (*Connector, error) {
		return NewConnector(cnt.ConnectorOptions()...), nil
	}),
	postgresenv.WithReuseStrategy(&ReuseStrategyCreateNewDB{}),
)

// Wrap for stdlib compatibility
stdlibEnv := postgresenv.NewStdlib(env)

// Use with dbenv helpers
db := dbenv.UseForTesting(t, stdlibEnv)

Example with options:

stdlibEnv := postgresenv.NewStdlib(env,
	stdlib.OptionBeforeAcquire(func(ctx context.Context, conn *pgx.Conn) bool {
		// Custom connection validation
		return conn.Ping(ctx) == nil
	}),
)

func (*StdlibEnvironment) Connect

func (d *StdlibEnvironment) Connect(ctx context.Context) (*sql.DB, error)

Connect establishes a database connection and returns a standard library *sql.DB. This method implements the dbenv.Environment interface, making it compatible with all testing utilities.

Connection Process:

  1. Connects to the underlying pgx environment
  2. Wraps the pgx pool with a stdlib connector
  3. Returns a *sql.DB ready for use with standard library APIs

The returned *sql.DB is fully functional and can be used with any database/sql driver methods. When the DB is closed (via db.Close()), the underlying pgx pool and all associated resources are automatically cleaned up.

Parameters:

  • ctx: Context for cancellation and timeout

Returns:

  • *sql.DB: Standard library database connection ready to use
  • error: Non-nil if connection fails

Example:

db, err := stdlibEnv.Connect(ctx)
if err != nil {
	return err
}
defer db.Close()

// Standard library usage
var count int
err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
	return err
}

fmt.Printf("User count: %d\n", count)

Directories

Path Synopsis
Package containers provides Docker container management for PostgreSQL test environments.
Package containers provides Docker container management for PostgreSQL test environments.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL