libdbexec

package module
v0.0.0-...-aabc709 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

README

libdbexec

Driver-agnostic interfaces for SQL access in Go: DBManager, Exec, and QueryRower. One codebase writes queries against these interfaces and runs unmodified against either PostgreSQL (lib/pq) or SQLite (modernc.org/sqlite).

Install

go get github.com/contenox/libdbexec

What it provides

  • DBManager — the entry point for a connection group. Obtain an Exec bound to auto-commit (WithoutTransaction) or to a transaction (WithTransaction), and Close the underlying pool on shutdown.

  • ExecExecContext, QueryContext, QueryRowContext, and DriverName() ("postgres" or "sqlite"). Implemented identically for both drivers via an internal txAwareDB that delegates to either *sql.DB or *sql.Tx.

  • QueryRower — wraps *sql.Row so Scan returns the package's ErrNotFound instead of the raw sql.ErrNoRows.

  • The WithTransaction / ReleaseTx patternWithTransaction returns an Exec, a CommitTx, and a ReleaseTx. Defer ReleaseTx immediately; it rolls back if the transaction wasn't committed and is a safe no-op otherwise (including after a successful commit).

  • A translated error-sentinel set — every driver maps its own errors (SQLite's string-based errors, PostgreSQL's pq.Error SQLSTATE codes) onto the same sentinels, checkable with errors.Is regardless of which backend is in use:

    ErrNotFound, ErrTxFailed, ErrMaxRowsReached,
    ErrUniqueViolation, ErrForeignKeyViolation, ErrNotNullViolation, ErrCheckViolation, ErrConstraintViolation,
    ErrDeadlockDetected, ErrSerializationFailure, ErrLockNotAvailable, ErrQueryCanceled,
    ErrDataTruncation, ErrNumericOutOfRange, ErrInvalidInputSyntax,
    ErrUndefinedColumn, ErrUndefinedTable
    
  • SetupLocalInstance (in localpostgres.go) — spins up an ephemeral PostgreSQL container via testcontainers-go for integration tests, and returns a ready-to-use connection string plus a cleanup func.

Usage

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/contenox/libdbexec"
)

func main() {
	ctx := context.Background()

	// Swap NewSQLiteDBManager for NewPostgresDBManager(ctx, dsn, schema) to
	// target PostgreSQL instead — the rest of the code is unchanged.
	mgr, err := libdbexec.NewSQLiteDBManager(ctx, "./local.db", `
		CREATE TABLE IF NOT EXISTS settings (
			key   TEXT PRIMARY KEY,
			value TEXT NOT NULL
		);
	`)
	if err != nil {
		log.Fatal(err)
	}
	defer mgr.Close()

	if err := updateSetting(ctx, mgr, "theme", "dark"); err != nil {
		log.Fatal(err)
	}
}

func updateSetting(ctx context.Context, mgr libdbexec.DBManager, key, value string) error {
	exec, commit, release, err := mgr.WithTransaction(ctx)
	if err != nil {
		return fmt.Errorf("begin transaction: %w", err)
	}
	defer release() // rolls back unless commit(ctx) already succeeded

	_, err = exec.ExecContext(ctx,
		"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",
		key, value)
	if err != nil {
		if errors.Is(err, libdbexec.ErrConstraintViolation) {
			return fmt.Errorf("invalid setting %q: %w", key, err)
		}
		return fmt.Errorf("update setting: %w", err)
	}

	var stored string
	if err := exec.QueryRowContext(ctx, "SELECT value FROM settings WHERE key = ?", key).Scan(&stored); err != nil {
		if errors.Is(err, libdbexec.ErrNotFound) {
			return fmt.Errorf("setting %q vanished mid-transaction: %w", key, err)
		}
		return fmt.Errorf("read back setting: %w", err)
	}

	return commit(ctx)
}

Drivers

Driver Constructor Underlying driver
SQLite NewSQLiteDBManager(ctx, path, schema) modernc.org/sqlite
PostgreSQL NewPostgresDBManager(ctx, dsn, schema) github.com/lib/pq

Both constructors open the connection group, ping it, and (if schema is non-empty) apply it before returning — intended for local/dev bootstrapping, not as a replacement for a real migration tool in production.

Documentation

Overview

Package libdbexec provides driver-agnostic interfaces (DBManager, Exec, QueryRower) for SQL access, implemented for PostgreSQL (lib/pq) and SQLite. WithTransaction pairs a CommitTx with a ReleaseTx meant for defer, and low-level driver errors are translated to package-level sentinels (ErrNotFound, ErrUniqueViolation, ErrDeadlockDetected, ...).

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound is returned by Scan when sql.ErrNoRows is encountered.
	ErrNotFound = errors.New("libdb: not found")

	// ErrTxFailed indicates a failure during transaction finalization (Commit or Rollback).
	ErrTxFailed = errors.New("libdb: transaction failed")

	// ErrMaxRowsReached indicates a table's configured maximum row count would be exceeded.
	ErrMaxRowsReached = errors.New("max row count reached")

	// ErrUniqueViolation corresponds to unique key constraint errors (e.g., PostgreSQL code 23505).
	ErrUniqueViolation = errors.New("libdb: unique constraint violation")
	// ErrForeignKeyViolation corresponds to foreign key constraint errors (e.g., PostgreSQL code 23503).
	ErrForeignKeyViolation = errors.New("libdb: foreign key violation")
	// ErrNotNullViolation corresponds to not-null constraint errors (e.g., PostgreSQL code 23502).
	ErrNotNullViolation = errors.New("libdb: not null constraint violation")
	// ErrCheckViolation corresponds to check constraint errors (e.g., PostgreSQL code 23514).
	ErrCheckViolation = errors.New("libdb: check constraint violation")
	// ErrConstraintViolation is a generic error for constraint violations not specifically mapped.
	ErrConstraintViolation = errors.New("libdb: constraint violation")

	// ErrDeadlockDetected corresponds to deadlock errors (e.g., PostgreSQL code 40P01).
	ErrDeadlockDetected = errors.New("libdb: deadlock detected")
	// ErrSerializationFailure corresponds to serialization failures (e.g., PostgreSQL code 40001).
	ErrSerializationFailure = errors.New("libdb: serialization failure")
	// ErrLockNotAvailable corresponds to lock acquisition failures (e.g., PostgreSQL code 55P03).
	ErrLockNotAvailable = errors.New("libdb: lock not available")
	// ErrQueryCanceled corresponds to query cancellation (e.g., PostgreSQL code 57014 or context cancellation).
	ErrQueryCanceled = errors.New("libdb: query canceled")

	// ErrDataTruncation corresponds to data truncation errors (e.g., PostgreSQL code 22001).
	ErrDataTruncation = errors.New("libdb: data truncation error")
	// ErrNumericOutOfRange corresponds to numeric overflow errors (e.g., PostgreSQL code 22003).
	ErrNumericOutOfRange = errors.New("libdb: numeric value out of range")
	// ErrInvalidInputSyntax corresponds to syntax errors in data representation (e.g., PostgreSQL code 22P02).
	ErrInvalidInputSyntax = errors.New("libdb: invalid input syntax")

	// ErrUndefinedColumn corresponds to referencing an unknown column (e.g., PostgreSQL code 42703).
	ErrUndefinedColumn = errors.New("libdb: undefined column")
	// ErrUndefinedTable corresponds to referencing an unknown table (e.g., PostgreSQL code 42P01).
	ErrUndefinedTable = errors.New("libdb: undefined table")
)

Predefined errors, checkable with errors.Is without relying on driver-specific error types or codes.

Functions

func SetupLocalInstance

func SetupLocalInstance(ctx context.Context, dbName, dbUser, dbPassword string) (string, *postgres.PostgresContainer, func(), error)

SetupLocalInstance starts an ephemeral PostgreSQL container for tests via testcontainers-go. It returns a ready-to-use connection string, the underlying container, and a cleanup func that stops the container. The cleanup func is always safe to call (even on error paths) except when SetupLocalInstance itself fails to start the container, in which case it returns a no-op cleanup.

Types

type CommitTx

type CommitTx func(ctx context.Context) error

CommitTx commits a transaction; call only on the success path. Returns nil, a wrapped ErrTxFailed, or a context error if ctx is done before the attempt.

type DBManager

type DBManager interface {
	// WithoutTransaction returns an executor operating directly on the connection
	// group, outside any transaction; each operation may run on a different connection.
	WithoutTransaction() Exec

	// WithTransaction starts a transaction and returns an Exec bound to it, a
	// CommitTx, and a ReleaseTx (idempotent, safe for defer, rolls back if not
	// committed). onRollback handlers run only after a successful rollback and
	// must not touch the transaction.
	WithTransaction(ctx context.Context, onRollback ...func()) (Exec, CommitTx, ReleaseTx, error)

	// Close terminates the underlying database connection group.
	Close() error
}

DBManager is the main entry point for database interactions: obtaining executors and managing the connection lifecycle. Typical usage starts a transaction with WithTransaction, defers the returned ReleaseTx immediately, does work through the returned Exec, then calls CommitTx on the success path.

func NewPostgresDBManager

func NewPostgresDBManager(ctx context.Context, dsn string, schema string) (DBManager, error)

NewPostgresDBManager creates a new DBManager for PostgreSQL. It opens a connection group using the provided DSN, pings the database to verify connectivity, and optionally executes an initial schema setup query. Note: For production schema management, using dedicated migration tools is recommended over passing a simple schema string here.

func NewSQLiteDBManager

func NewSQLiteDBManager(ctx context.Context, path string, schema string) (DBManager, error)

NewSQLiteDBManager creates a new DBManager for SQLite. path is the database file path (e.g. "./.contenox/local.db" or "file:local.db"). The parent directory is created if missing. schema is applied on open (e.g. runtimetypes.SchemaSQLite).

type Exec

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

	// QueryContext executes a query returning rows. Callers must check rows.Err() after iterating.
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)

	// QueryRowContext always returns a non-nil QueryRower; errors surface from its Scan.
	QueryRowContext(ctx context.Context, query string, args ...any) QueryRower

	// DriverName returns the database driver name ("postgres", "sqlite").
	DriverName() string
}

Exec is the common interface for executing database operations, whether within a transaction or directly on the connection group. Implementations must translate driver errors into the package's Err* sentinels.

type QueryRower

type QueryRower interface {
	// Scan returns ErrNotFound if no row matched; other errors are translated too.
	Scan(dest ...any) error
}

QueryRower wraps *sql.Row so Scan errors (like sql.ErrNoRows) are translated consistently.

type ReleaseTx

type ReleaseTx func() error

ReleaseTx rolls back a transaction if it wasn't committed and is a no-op otherwise; idempotent and meant for defer.

Jump to

Keyboard shortcuts

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