postgres

package
v0.0.0-...-c12ca72 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2025 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package postgres provides PostgreSQL test database support for testdb.

Choosing the Right API

This package provides two main functions: Setup() and New(). Here's how to choose:

Use Setup() when:

  • You're using pgx/pgxpool directly in your application
  • You don't need custom initialization logic
  • You want the simplest API (just get a pool and test)

Use New() when:

  • You're using an ORM (GORM, ent, SQLBoiler)
  • You're using database/sql interfaces (use SqlDbInitializer)
  • You're using sqlx for struct scanning
  • You have custom connection pooling requirements
  • You need to wrap the connection in your own type
  • You need custom tracing/logging/instrumentation

Basic Usage with Setup()

import (
    "github.com/bashhack/testdb"
    "github.com/bashhack/testdb/postgres"
)

func TestUsers(t *testing.T) {
    pool := postgres.Setup(t,
        testdb.WithMigrations("./migrations"),
        testdb.WithMigrationTool(testdb.MigrationToolTern))

    _, err := pool.Exec(ctx, "INSERT INTO users (email) VALUES ($1)", "test@example.com")
    require.NoError(t, err)
}

Built-in Initializers

The package provides two built-in initializers:

PoolInitializer (default) - Creates *pgxpool.Pool for PostgreSQL-specific features:

pool := postgres.Setup(t)  // Uses PoolInitializer
// or
db := postgres.New(t, &postgres.PoolInitializer{})
pool := db.Entity().(*pgxpool.Pool)

SqlDbInitializer - Creates *sql.DB for database/sql compatibility:

db := postgres.New(t, &postgres.SqlDbInitializer{})
sqlDB := db.Entity().(*sql.DB)
// Use standard database/sql operations
sqlDB.QueryRow("SELECT * FROM users WHERE id = $1", 1)

Custom Initializer Examples

When your application uses an ORM like GORM, you need New() with a custom initializer:

type GormInitializer struct{}

func (g *GormInitializer) InitializeTestDatabase(ctx context.Context, dsn string) (any, error) {
    return gorm.Open(postgres.Open(dsn), &gorm.Config{})
}

func TestWithGORM(t *testing.T) {
    db := postgres.New(t, &GormInitializer{})
    gormDB := db.Entity().(*gorm.DB)
    // Now you can call your application functions that expect *gorm.DB
}

When your application wraps connections in a custom type:

type AppDB struct {
    Pool    *pgxpool.Pool
    Timeout time.Duration
}

type AppDBInitializer struct{}

func (a *AppDBInitializer) InitializeTestDatabase(ctx context.Context, dsn string) (any, error) {
    pool, err := pgxpool.New(ctx, dsn)
    if err != nil {
        return nil, err
    }
    return &AppDB{Pool: pool, Timeout: 30 * time.Second}, nil
}

func TestWithCustomType(t *testing.T) {
    db := postgres.New(t, &AppDBInitializer{})
    appDB := db.Entity().(*AppDB)
    // Now you can call your application functions that expect *AppDB
}
Example (BasicUsage)

Example_basicUsage demonstrates creating an isolated test database.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	// Create isolated test database
	pool := postgres.Setup(t)

	// Use the database - it's completely isolated
	// Cleanup is automatic via t.Cleanup()
	var result int
	err := pool.QueryRow(context.Background(), "SELECT 1").Scan(&result)
	if err != nil {
		fmt.Printf("Query failed: %v\n", err)
		return
	}

	fmt.Printf("Query result: %d\n", result)

}
Output:

Query result: 1
Example (Configuration)

Example_configuration demonstrates configuration options.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb"
	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	pool := postgres.Setup(t,
		testdb.WithMigrations("../testdata/postgres/migrations_migrate"),
		testdb.WithMigrationTool(testdb.MigrationToolMigrate),
		testdb.WithDBPrefix("myapp_test"),
	)

	if err := pool.Ping(context.Background()); err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("Database configured successfully")
	fmt.Println("Custom prefix and migrations applied")

}
Output:

Database configured successfully
Custom prefix and migrations applied
Example (CustomPrefix)

Example_customPrefix demonstrates using a custom database name prefix.

package main

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"github.com/bashhack/testdb"
	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	pool := postgres.Setup(t,
		testdb.WithDBPrefix("myapp"),
	)

	// Database name will be like "myapp_1699564231_a1b2c3d4"
	var dbname string
	err := pool.QueryRow(context.Background(), "SELECT current_database()").Scan(&dbname)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Verify prefix is used
	if len(dbname) < 6 || dbname[:6] != "myapp_" {
		fmt.Printf("Expected database name to start with 'myapp_', got: %s\n", dbname)
		return
	}

	fmt.Println("Custom prefix applied successfully")
	fmt.Printf("Database name starts with: %s\n", strings.Split(dbname, "_")[0])

}
Output:

Custom prefix applied successfully
Database name starts with: myapp
Example (Gorm)

Example_gorm demonstrates using postgres.New() with GORM for custom database initialization.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb/postgres"

	gormpostgres "gorm.io/driver/postgres"
	"gorm.io/gorm"
)

// GormExampleInitializer is a GORM initializer for examples
type GormExampleInitializer struct{}

func (g *GormExampleInitializer) InitializeTestDatabase(ctx context.Context, dsn string) (any, error) {
	return gorm.Open(gormpostgres.Open(dsn), &gorm.Config{})
}

func main() {
	t := &testing.T{}

	// Create test database with GORM initializer
	// postgres.New() is used instead of postgres.Setup() when you need custom initialization
	db := postgres.New(t, &GormExampleInitializer{})

	// Get GORM DB instance
	gormDB := db.Entity().(*gorm.DB)

	// Define a model
	type Article struct {
		ID      uint   `gorm:"primaryKey"`
		Title   string `gorm:"not null"`
		Content string
	}

	// Auto-migrate schema
	if err := gormDB.AutoMigrate(&Article{}); err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Create a record
	article := Article{Title: "Getting Started", Content: "Introduction to testdb"}
	if err := gormDB.Create(&article).Error; err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Query the record
	var found Article
	if err := gormDB.First(&found, "title = ?", "Getting Started").Error; err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("GORM setup complete")
	fmt.Printf("Article created: %s\n", found.Title)

}
Output:

GORM setup complete
Article created: Getting Started
Example (HelperFunction)

Example_helperFunction demonstrates the helper function pattern for consistent test setup.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb"
	"github.com/bashhack/testdb/postgres"
	"github.com/jackc/pgx/v5/pgxpool"
)

func main() {
	setupDB := func(t *testing.T) *pgxpool.Pool {
		t.Helper()
		return postgres.Setup(t,
			testdb.WithMigrations("../testdata/postgres/migrations_migrate"),
			testdb.WithMigrationTool(testdb.MigrationToolMigrate),
			testdb.WithDBPrefix("myapp_test"),
		)
	}

	t := &testing.T{}
	pool := setupDB(t)

	if err := pool.Ping(context.Background()); err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("Helper function setup complete")

}
Output:

Helper function setup complete
Example (MigrationsWithGoose)

Example_migrationsWithGoose demonstrates using goose.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb"
	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	pool := postgres.Setup(t,
		testdb.WithMigrations("../testdata/postgres/migrations_goose"),
		testdb.WithMigrationTool(testdb.MigrationToolGoose),
	)

	// Verify migration created the products table
	var exists bool
	err := pool.QueryRow(context.Background(), `
		SELECT EXISTS (
			SELECT FROM information_schema.tables
			WHERE table_name = 'products'
		)
	`).Scan(&exists)

	if err != nil {
		fmt.Printf("Failed to check table: %v\n", err)
		return
	}

	if !exists {
		fmt.Println("Expected products table to exist after migrations")
		return
	}

	fmt.Println("Migration with goose successful")
	fmt.Println("Products table exists")

}
Output:

Migration with goose successful
Products table exists
Example (MigrationsWithMigrate)

Example_migrationsWithMigrate demonstrates using golang-migrate.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb"
	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	pool := postgres.Setup(t,
		testdb.WithMigrations("../testdata/postgres/migrations_migrate"),
		testdb.WithMigrationTool(testdb.MigrationToolMigrate),
	)

	// Verify migration created the table
	var exists bool
	err := pool.QueryRow(context.Background(), `
		SELECT EXISTS (
			SELECT FROM information_schema.tables
			WHERE table_name = 'test_table'
		)
	`).Scan(&exists)

	if err != nil {
		fmt.Printf("Failed to check table: %v\n", err)
		return
	}

	if !exists {
		fmt.Println("Expected test_table to exist after migrations")
		return
	}

	// Insert data to verify table works
	_, err = pool.Exec(context.Background(),
		"INSERT INTO test_table (name) VALUES ($1)", "test")
	if err != nil {
		fmt.Printf("Failed to insert: %v\n", err)
		return
	}

	fmt.Println("Migration with golang-migrate successful")
	fmt.Println("Table exists and data inserted")

}
Output:

Migration with golang-migrate successful
Table exists and data inserted
Example (MigrationsWithTern)

Example_migrationsWithTern demonstrates using tern.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb"
	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	pool := postgres.Setup(t,
		testdb.WithMigrations("../testdata/postgres/migrations_tern"),
		testdb.WithMigrationTool(testdb.MigrationToolTern),
	)

	// Verify migration created the users table
	var exists bool
	err := pool.QueryRow(context.Background(), `
		SELECT EXISTS (
			SELECT FROM information_schema.tables
			WHERE table_name = 'users'
		)
	`).Scan(&exists)

	if err != nil {
		fmt.Printf("Failed to check table: %v\n", err)
		return
	}

	if !exists {
		fmt.Println("Expected users table to exist after migrations")
		return
	}

	fmt.Println("Migration with tern successful")
	fmt.Println("Users table exists")

}
Output:

Migration with tern successful
Users table exists
Example (MultipleOptions)

Example_multipleOptions demonstrates combining multiple configuration options.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb"
	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	pool := postgres.Setup(t,
		testdb.WithDBPrefix("test"),
		testdb.WithMigrations("../testdata/postgres/migrations_migrate"),
		testdb.WithMigrationTool(testdb.MigrationToolMigrate),
	)

	// Verify database exists and migrations ran
	var dbname string
	err := pool.QueryRow(context.Background(), "SELECT current_database()").Scan(&dbname)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Check migration ran
	var exists bool
	err = pool.QueryRow(context.Background(), `
		SELECT EXISTS (
			SELECT FROM information_schema.tables
			WHERE table_name = 'test_table'
		)
	`).Scan(&exists)

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	if !exists {
		fmt.Println("Expected test_table to exist")
		return
	}

	fmt.Println("Multiple options configured successfully")
	fmt.Println("Database created with custom prefix")
	fmt.Println("Migrations ran and tables exist")

}
Output:

Multiple options configured successfully
Database created with custom prefix
Migrations ran and tables exist
Example (Parallel)

Example_parallel shows running tests in parallel with isolated databases.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb"
	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	t.Run("create user", func(t *testing.T) {
		t.Parallel()

		pool := postgres.Setup(t,
			testdb.WithMigrations("../testdata/postgres/migrations_migrate"),
			testdb.WithMigrationTool(testdb.MigrationToolMigrate))

		_, err := pool.Exec(context.Background(),
			"INSERT INTO users (email) VALUES ($1)", "user1@example.com")
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}
		fmt.Println("User 1 created")
	})

	t.Run("delete user", func(t *testing.T) {
		t.Parallel()

		pool := postgres.Setup(t,
			testdb.WithMigrations("../testdata/postgres/migrations_migrate"),
			testdb.WithMigrationTool(testdb.MigrationToolMigrate))

		_, err := pool.Exec(context.Background(),
			"INSERT INTO users (email) VALUES ($1)", "user2@example.com")
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}
		fmt.Println("User 2 created")
	})

	// Note: This example demonstrates the API but cannot be executed as a runnable
	// example because it uses t.Run() which requires a real testing.T instance.
}
Example (ParallelExecution)

Example_parallelExecution demonstrates running tests in parallel with isolated databases.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	t.Run("test1", func(t *testing.T) {
		t.Parallel()

		pool := postgres.Setup(t)

		// Each test gets its own database
		var dbname string
		err := pool.QueryRow(context.Background(), "SELECT current_database()").Scan(&dbname)
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}

		fmt.Printf("Test1 using database: %s\n", dbname)
	})

	t.Run("test2", func(t *testing.T) {
		t.Parallel()

		pool := postgres.Setup(t)

		// This database is different from test1
		var dbname string
		err := pool.QueryRow(context.Background(), "SELECT current_database()").Scan(&dbname)
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}

		fmt.Printf("Test2 using database: %s\n", dbname)
	})

	t.Run("test3", func(t *testing.T) {
		t.Parallel()

		pool := postgres.Setup(t)

		// And this is yet another isolated database
		var dbname string
		err := pool.QueryRow(context.Background(), "SELECT current_database()").Scan(&dbname)
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}

		fmt.Printf("Test3 using database: %s\n", dbname)
	})

	// Note: This example demonstrates the API but cannot be executed as a runnable
	// example because it uses t.Run() which requires a real testing.T instance.
}
Example (Setup)

Example_setup demonstrates basic usage of postgres.Setup for creating isolated test databases with automatic cleanup.

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb"
	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	// Create isolated test database with migrations
	// Cleanup is automatic via t.Cleanup()
	pool := postgres.Setup(t,
		testdb.WithMigrations("../testdata/postgres/migrations_migrate"),
		testdb.WithMigrationTool(testdb.MigrationToolMigrate))

	_, err := pool.Exec(context.Background(),
		"INSERT INTO test_table (name) VALUES ($1)", "test")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("Database setup complete")
	fmt.Println("Data inserted successfully")

}
Output:

Database setup complete
Data inserted successfully
Example (SetupVsNew)

Example_setupVsNew demonstrates when to use Setup() vs New().

package main

import (
	"context"
	"fmt"
	"testing"

	"github.com/bashhack/testdb/postgres"
	"github.com/jackc/pgx/v5/pgxpool"
)

func main() {
	t := &testing.T{}

	// Use Setup() when working with pgx/pgxpool directly
	// Returns *pgxpool.Pool - simplest API
	pool := postgres.Setup(t)

	var result int
	err := pool.QueryRow(context.Background(), "SELECT 1").Scan(&result)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Printf("Setup() returns pool, result: %d\n", result)

	// Use New() when you need custom initialization (GORM, sqlx, etc.)
	// Returns *testdb.TestDatabase with more flexibility
	db := postgres.New(t, &postgres.PoolInitializer{})
	pool2 := db.Entity().(*pgxpool.Pool)

	var result2 int
	err = pool2.QueryRow(context.Background(), "SELECT 2").Scan(&result2)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Printf("New() returns TestDatabase, result: %d\n", result2)
	fmt.Printf("Database name starts with: %s\n", db.Name()[:5]) // Show prefix only

}
Output:

Setup() returns pool, result: 1
New() returns TestDatabase, result: 2
Database name starts with: test_
Example (SqlDB)

Example_sqlDB demonstrates using postgres.New() with SqlDbInitializer for database/sql compatibility.

Use SqlDbInitializer when:

  • Your application uses database/sql interfaces
  • You need compatibility with existing sql.DB-based code
  • You're working with libraries that expect *sql.DB

For PostgreSQL-specific features, use PoolInitializer or Setup() instead.

package main

import (
	"database/sql"
	"fmt"
	"testing"

	"github.com/bashhack/testdb/postgres"
)

func main() {
	t := &testing.T{}

	// Create test database with sql.DB
	db := postgres.New(t, &postgres.SqlDbInitializer{})

	// Get the *sql.DB instance
	sqlDB := db.Entity().(*sql.DB)

	// Create table
	_, err := sqlDB.Exec(`
		CREATE TABLE users (
			id SERIAL PRIMARY KEY,
			name TEXT NOT NULL,
			email TEXT UNIQUE NOT NULL
		)
	`)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Insert data
	_, err = sqlDB.Exec(
		`INSERT INTO users (name, email) VALUES ($1, $2)`,
		"Alice", "alice@example.com",
	)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Query data
	var name string
	err = sqlDB.QueryRow(
		`SELECT name FROM users WHERE email = $1`,
		"alice@example.com",
	).Scan(&name)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("sql.DB setup complete")
	fmt.Printf("Found user: %s\n", name)

	// Cleanup happens automatically via t.Cleanup()

}
Output:

sql.DB setup complete
Found user: Alice

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(t testing.TB, initializer testdb.DBInitializer, opts ...testdb.Option) *testdb.TestDatabase

New creates a PostgreSQL test database with a custom initializer.

When to use New():

  • You're using GORM, sqlx, ent, or another ORM/database wrapper
  • You have custom initialization logic (connection pooling, logging, tracing)
  • You need access to the TestDatabase (for DSN, config, or manual migration control)
  • You want to type-assert to your own database type

When to use Setup() instead:

  • You're using pgx/pgxpool directly (simpler API)
  • You don't need custom initialization

When to use testdb.New() instead:

  • You need manual cleanup control (not automatic via t.Cleanup)
  • You're implementing a custom database provider

The function:

  1. Creates an isolated test database
  2. Runs migrations if configured
  3. Returns a *testdb.TestDatabase with custom entity
  4. Registers cleanup via t.Cleanup() - no manual cleanup needed

IMPORTANT: Do NOT call db.Close() or manually close the entity. The function automatically registers cleanup via t.Cleanup() that will:

  1. Close the entity (pool, GORM db, sqlx db, etc.) if it implements io.Closer
  2. Drop the test database
  3. Clean up provider resources

Calls t.Fatal() on any error.

Example with GORM:

type GormInitializer struct{}

func (g *GormInitializer) InitializeTestDatabase(ctx context.Context, dsn string) (any, error) {
    return gorm.Open(postgres.Open(dsn), &gorm.Config{})
}

func TestAdvanced(t *testing.T) {
    db := postgres.New(t, &GormInitializer{},
        testdb.WithMigrations("./migrations"),
        testdb.WithMigrationTool(testdb.MigrationToolGoose))
    gormDB := db.Entity().(*gorm.DB)
    // Use gormDB for testing - NO defer needed, cleanup is automatic!
}

Example with sqlx:

type SqlxInitializer struct{}

func (s *SqlxInitializer) InitializeTestDatabase(ctx context.Context, dsn string) (any, error) {
    return sqlx.Connect("postgres", dsn)
}

func TestSqlx(t *testing.T) {
    db := postgres.New(t, &SqlxInitializer{})
    sqlxDB := db.Entity().(*sqlx.DB)
    // Use sqlxDB for testing - NO defer needed, cleanup is automatic!
}

func Setup

func Setup(t testing.TB, opts ...testdb.Option) *pgxpool.Pool

Setup creates a PostgreSQL test database and returns a ready-to-use connection pool.

When to use Setup():

  • You're using pgx/pgxpool directly (recommended for most PostgreSQL applications)
  • You want the simplest API - just get a pool and start testing
  • You don't need custom initialization logic

When to use New() instead:

  • You need a custom initializer (GORM, sqlx, ent, or custom wrapper)
  • You need access to the TestDatabase for DSN or configuration
  • You need to run migrations manually (not during setup)

When to use testdb.New() instead:

  • You need fine-grained control over cleanup timing
  • You're implementing a custom database provider

The function:

  1. Creates an isolated test database
  2. Runs migrations if configured
  3. Returns a *pgxpool.Pool ready for testing
  4. Registers cleanup via t.Cleanup() - no manual cleanup needed

IMPORTANT: Do NOT call pool.Close() or defer any cleanup. The function automatically registers cleanup that will run after your test.

Calls t.Fatal() on any error.

Example:

func TestUsers(t *testing.T) {
    pool := postgres.Setup(t,
        testdb.WithMigrations("./migrations"),
        testdb.WithMigrationTool(testdb.MigrationToolTern))
    // Use pool for testing - NO defer pool.Close() needed!
    // Cleanup happens automatically via t.Cleanup()
}

Types

type PoolInitializer

type PoolInitializer struct {
	// ConfigModifier allows customization of the pool configuration after
	// the DSN is parsed but before the pool is created.
	// If nil, sensible defaults for testing are applied.
	ConfigModifier func(*pgxpool.Config)
}

PoolInitializer is the default initializer for PostgreSQL connections. It creates a pgxpool.Pool with sensible defaults for testing.

func (*PoolInitializer) InitializeTestDatabase

func (pi *PoolInitializer) InitializeTestDatabase(ctx context.Context, dsn string) (any, error)

InitializeTestDatabase creates a pgxpool.Pool for the test database.

type PostgresProvider

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

PostgresProvider implements testdb.Provider for PostgreSQL. It handles database creation, deletion, and connection management.

func (*PostgresProvider) BuildDSN

func (p *PostgresProvider) BuildDSN(dbName string) (string, error)

BuildDSN constructs a PostgreSQL connection string (DSN) for the specified database. It uses the cached admin config to avoid re-parsing on every call.

func (*PostgresProvider) Cleanup

func (p *PostgresProvider) Cleanup(ctx context.Context) error

Cleanup performs the necessary cleanup of the provider's resources. This includes closing the admin database connection.

func (*PostgresProvider) CreateDatabase

func (p *PostgresProvider) CreateDatabase(ctx context.Context, name string) error

CreateDatabase creates a new PostgreSQL database with the given name.

func (*PostgresProvider) DropDatabase

func (p *PostgresProvider) DropDatabase(ctx context.Context, name string) error

DropDatabase drops a PostgreSQL database if it exists. Retries on SQLSTATE 55006 to handle the race where pg_terminate_backend() has sent termination signals but connections haven't fully closed yet. This is especially important under high concurrency when multiple databases are being dropped simultaneously.

func (*PostgresProvider) Initialize

func (p *PostgresProvider) Initialize(ctx context.Context, cfg testdb.Config) error

Initialize sets up the PostgreSQL provider with admin credentials. It establishes a connection to the admin database which will be used for creating and managing test databases.

func (*PostgresProvider) ResolvedAdminDSN

func (p *PostgresProvider) ResolvedAdminDSN() string

ResolvedAdminDSN returns the resolved admin DSN being used by this provider. This is the actual DSN after resolving user overrides, environment variables, and defaults. Useful for migrations and other operations that need the admin connection string.

func (*PostgresProvider) TerminateConnections

func (p *PostgresProvider) TerminateConnections(ctx context.Context, name string) error

TerminateConnections forcefully terminates all connections to the specified database. This is necessary before dropping a database, as active connections will prevent deletion.

This implementation uses a two-step approach to handle the race condition between pool.Close() and pg_stat_activity updates: 1. DISALLOW new connections (prevents races) 2. TERMINATE existing connections

type SqlDbInitializer

type SqlDbInitializer struct{}

SqlDbInitializer creates a standard *sql.DB connection using pgx's database/sql driver.

This uses pgx/v5/stdlib, which implements the database/sql driver interface. You get the standard library's *sql.DB type with pgx's PostgreSQL implementation underneath, providing better PostgreSQL support than lib/pq while maintaining database/sql compatibility.

Use SqlDbInitializer when:

  • Your application code uses database/sql interfaces (*sql.DB, *sql.Tx, *sql.Rows)
  • You're working with libraries that expect *sql.DB (some ORMs, query builders)
  • You need compatibility with existing sql.DB-based code
  • You want standard database/sql semantics (connection pooling, transactions, prepared statements)

For PostgreSQL-specific features (arrays, JSON types, COPY, LISTEN/NOTIFY), use PoolInitializer instead which provides *pgxpool.Pool with full pgx capabilities.

Example:

db := postgres.New(t, &postgres.SqlDbInitializer{})
sqlDB := db.Entity().(*sql.DB)

// Use standard database/sql operations
_, err := sqlDB.Exec("INSERT INTO users (name) VALUES ($1)", "Alice")
var name string
err = sqlDB.QueryRow("SELECT name FROM users WHERE id = $1", 1).Scan(&name)

func (*SqlDbInitializer) InitializeTestDatabase

func (si *SqlDbInitializer) InitializeTestDatabase(ctx context.Context, dsn string) (any, error)

InitializeTestDatabase creates a *sql.DB using the "pgx" driver (pgx/v5/stdlib). The connection is verified via Ping before being returned.

Returns an error if the connection cannot be established or verified. On error, the database connection is automatically closed.

Jump to

Keyboard shortcuts

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