Help us shape the future of Go! Take the 2026 Go Developer Survey

sql

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package sql wraps a PostgreSQL connection with context-scoped transactions, Datadog tracing and typed errors.

A connection is built from a Config and used through the Connection interface:

conn, err := sql.New(sql.Config{
	Host: "localhost", Port: "5432",
	User: "postgres", Password: pw,
	Database: "orders", SSLMode: "disable",
	MaxOpenConn: 10, MaxIdleConn: 5,
})

var o Order
err = conn.Get(ctx, &o, "SELECT id, total FROM orders WHERE id = $1", id)

Scanning is sqlx over the pgx stdlib driver: columns map to struct fields by lowercased field name, or by an explicit `db` tag, and Select fills a slice.

Transactions travel in the context rather than in a separate handle. TransactionContext begins one, puts it in the context it passes to fn, and commits when fn returns nil or rolls back when it returns an error:

err := conn.TransactionContext(ctx, func(ctx context.Context) error {
	if _, err := conn.Exec(ctx, debit, from); err != nil {
		return err
	}
	_, err := conn.Exec(ctx, credit, to)
	return err
})

The point of that shape is that repository code keeps calling the same Connection methods either way: Get, Select and Exec look for a transaction in the context and use it when there is one, or go to the pool when there is not. Nothing has to be threaded through a separate parameter.

Driver errors are translated. Known PostgreSQL SQLSTATE codes become the sentinel errors declared in this package, so callers match with errors.Is instead of comparing strings:

if errors.Is(err, sql.ErrUniqueViolation) { ... }

Anything unmapped becomes ErrGeneral. Note that the translation keeps the message but not the *pgconn.PgError itself, so details such as the constraint name are not reachable from the returned error.

Config carries a password. Its String method redacts it, but the dsn built for the driver does not - never log the value returned by an internal helper, and prefer %v on the Config, which goes through String.

Every method on the connection opens a Datadog span tagged with the query.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (

	// ErrConn represents an error while connecting to the database.
	ErrConn = errors.New("error while connecting to database")

	// ErrSyntax represents a syntax error with code 42601, in which the query is malformed.
	ErrSyntax = errors.New("syntax Error 42601")

	// ErrUniqueViolation represents a unique violation error with code 23505, in which the value
	// already exists in the database and can't be duplicated.
	ErrUniqueViolation = errors.New("unique violation 23505")

	// ErrForeignKeyViolation represents a foreign key violation error with code 23503, in which the FK does
	// not exist.
	ErrForeignKeyViolation = errors.New("foreign key violation 23503")

	// ErrNoParameter represents a no parameter error with code 42P02, that may happen when a parameter is missing
	// for example: 'INSERT INTO deals (affiliate_id, value) VALUES ($1, $2)', where $1 is missing.
	ErrNoParameter = errors.New("no parameter 42P02")

	// ErrGeneral represents any err that has not been mapped yet, the err then should be asserted to
	// the pgconn.PgError type.
	ErrGeneral = errors.New("something went wrong")
)

Errors that can be returned by the sql package.

Functions

func WithTransaction

func WithTransaction(ctx context.Context, transaction Transaction) context.Context

WithTransaction returns a context carrying transaction, so that later calls to Get, Select and Exec on a Connection run inside it.

TransactionContext does this for you. Call it directly only when managing a transaction by hand.

Types

type Config

type Config struct {
	Host            string
	Port            string
	User            string
	Password        string
	Database        string
	SSLMode         string
	MaxOpenConn     int
	ConnMaxIdleTime time.Duration
	ConnMaxLifetime time.Duration
	MaxIdleConn     int
}

Config describes how to reach a PostgreSQL server and how to size the pool.

The zero value of the pool fields means the database/sql defaults: unlimited open connections, and no maximum idle time or lifetime.

Password is redacted by String, so log the Config itself rather than any string built from its fields.

func (Config) String

func (cfg Config) String() string

String implements fmt.Stringer with the password redacted. Config is a plausible thing to log, and without this method every %v on a Config - or on any struct holding one - would print the credential in clear text.

type Connection

type Connection interface {
	Queryer
	Execer
	Begin(ctx context.Context) (Transaction, error)
	TransactionContext(ctx context.Context, fn func(ctx context.Context) error) error
	Ping() error
	Close() error
}

Connection is a PostgreSQL connection pool.

Get, Select and Exec look for a transaction in the context and use it when present, so the same call works inside and outside TransactionContext.

Ping and Close take no context and cannot be cancelled.

func New

func New(cfg Config) (Connection, error)

New opens a connection pool and verifies it with a ping.

It returns an error wrapping ErrConn when the server cannot be reached.

Example
package main

import (
	"context"
	"fmt"

	dbsql "github.com/diegodesousas/go-devkit/pkg/database/sql"
)

type account struct {
	ID      int    `db:"id"`
	Owner   string `db:"owner"`
	Balance int    `db:"balance"`
}

// connect is the shared set-up for the examples below. Every one of them needs a
// live PostgreSQL server, so they are compiled but not run.
func connect() dbsql.Connection {
	conn, err := dbsql.New(dbsql.Config{
		Host:        "localhost",
		Port:        "5432",
		User:        "postgres",
		Password:    "secret",
		Database:    "bank",
		SSLMode:     "disable",
		MaxOpenConn: 10,
		MaxIdleConn: 5,
	})
	if err != nil {
		panic(err)
	}

	return conn
}

func main() {
	conn := connect()
	defer func() { _ = conn.Close() }()

	ctx := context.Background()

	var a account
	if err := conn.Get(ctx, &a, "SELECT id, owner, balance FROM accounts WHERE id = $1", 1); err != nil {
		panic(err)
	}
	fmt.Println(a.Owner, a.Balance)

	var all []account
	if err := conn.Select(ctx, &all, "SELECT id, owner, balance FROM accounts"); err != nil {
		panic(err)
	}
	fmt.Println(len(all))
}

type Execer

type Execer interface {
	Exec(ctx context.Context, query string, args ...any) (sql.Result, error)
}

Execer runs a statement that returns no rows.

type Queryer

type Queryer interface {
	Get(ctx context.Context, dest any, query string, args ...any) error
	Select(ctx context.Context, dest any, query string, args ...any) error
}

Queryer reads rows. Get scans a single row into dest and returns sql.ErrNoRows when there is none; Select scans every row into a slice.

type Transaction

type Transaction interface {
	Queryer
	Execer
	Commit() error
	Rollback() error
}

Transaction is an open database transaction. Prefer Connection.TransactionContext, which commits and rolls back for you; use this directly only when the transaction has to outlive a single function.

Directories

Path Synopsis
Package upsert builds PostgreSQL INSERT ...
Package upsert builds PostgreSQL INSERT ...

Jump to

Keyboard shortcuts

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