sqlconn

sqlconn manages a database/sql connection lifecycle in long-running Go services: applying pool limits, verifying connectivity, exposing health checks, and closing the connection on shutdown signals.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/sqlconn"

Package sqlconn manages a database/sql connection lifecycle in long-running Go services: applying pool limits, verifying connectivity, exposing health checks, and closing the connection on shutdown signals.

How It Works

  • New creates a connection from explicit driver and dsn values.
  • Connect accepts a URL-like string in the form <DRIVER>://<DSN> and delegates to New. If only the DSN is provided, a driver can be supplied via WithDefaultDriver.
  • The context passed to New/Connect bounds connection establishment only (dialing and the initial health check). It does NOT control the pool lifetime: a request- or timeout-scoped context will not close the pool when it ends. To close the pool on application shutdown, wire a long-lived context via WithLifetimeContext and/or a shutdown channel via WithShutdownSignalChan, or call SQLConn.Shutdown directly.
  • On successful connect, pool settings are applied (max idle/open, idle time, lifetime), and a goroutine waits for a shutdown signal channel, a canceled lifetime context, or a direct SQLConn.Shutdown call.
  • When shutdown is triggered, SQLConn.Shutdown closes the underlying database handle, updates the shared shutdown wait group, and prevents further use by setting the internal DB pointer to nil. Shutdown is idempotent and also stops the watcher goroutine, so the watcher and a deferred call can both fire safely without leaking the goroutine.

Usage

Minimal: the deferred SQLConn.Shutdown closes the pool and stops the watcher. The context bounds establishment only, so it is safe to pass a short-lived one.

c, err := sqlconn.Connect(
    ctx,
    "mysql://user:pass@tcp(localhost:3306)/appdb",
    sqlconn.WithDefaultDriver("mysql"),
)
if err != nil {
    return err
}

if err := c.HealthCheck(ctx); err != nil {
    return err
}

defer c.Shutdown(ctx)

Service lifecycle: wire the connection into a shared shutdown channel and wait group so a central signal closes every pool and the process waits for them.

c, err := sqlconn.Connect(
    ctx,
    "mysql://user:pass@tcp(localhost:3306)/appdb",
    sqlconn.WithDefaultDriver("mysql"),
    sqlconn.WithShutdownSignalChan(shutdownCh), // close(shutdownCh) closes the pool
    sqlconn.WithShutdownWaitGroup(&shutdownWG), // shutdownWG.Wait() blocks until closed
)
if err != nil {
    return err
}

When To Use

  • Pool limits, a connectivity check, a health probe, and shutdown handling all belong in one place.
  • The connection must close cleanly when the service receives a shutdown signal.

Example

// A real program registers a driver (for example go-sql-driver/mysql)
// and lets sqlconn call sql.Open. WithSQLOpenFunc substitutes a mock so
// the example needs no database.
mockDB, mock, err := sqlmock.New()
if err != nil {
	fmt.Println(err)

	return
}

// The connection check runs the validation query, and Shutdown closes
// the pool.
mock.ExpectQuery("SELECT 1").WillReturnRows(sqlmock.NewRows([]string{"1"}).AddRow(1))
mock.ExpectClose()

conn, err := sqlconn.New(
	context.TODO(),
	"mysql",
	"user:pass@tcp(127.0.0.1:3306)/testdb",
	sqlconn.WithConnMaxOpen(25),
	sqlconn.WithConnMaxIdleCount(5),
	sqlconn.WithConnMaxLifetime(5*time.Minute),
	sqlconn.WithPingTimeout(2*time.Second),
	sqlconn.WithSQLOpenFunc(func(_, _ string) (*sql.DB, error) {
		return mockDB, nil
	}),
)
if err != nil {
	fmt.Println(err)

	return
}

defer func() { _ = conn.Shutdown(context.TODO()) }()

// DB returns the pooled *sql.DB for normal query execution.
fmt.Println(conn.DB() != nil)

// Output:
// true

Full source is in example_sqlconn_test.go. More runnable examples are on pkg.go.dev.

Dependencies

This package reaches no external module: it uses only the Go standard library.