db

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Feb 9, 2026 License: MIT Imports: 12 Imported by: 0

README

one/db

A lightweight SQLite database abstraction for Go with a stdlib-compatible API and generic scanning.

Features

  • stdlib-compatible API: Familiar methods like QueryContext, QueryRowContext, ExecContext, BeginTx
  • Generic scanning: Type-safe Scan[T] and ScanAll[T] functions eliminate boilerplate
  • Iterator pattern: Uses Go 1.23+ iter.Seq2[T, error] for efficient row iteration
  • Hybrid column mapping: Automatic snake_case conversion or explicit db struct tags
  • Flexible SELECT * support: Works with any column order (in ScanAll)
  • NULL handling: Supports both pointer types (NULL → nil) and zero values
  • Environment-based init: Database path from APP_NAME environment variable

Installation

go get github.com/michaldziurowski/one/[email protected]

Requirements: Go 1.24+

Quick Start

package main

import (
    "context"
    "log"
    "github.com/michaldziurowski/one/db"
)

type User struct {
    ID       int    `db:"id"`
    UserName string
    Email    string
}

func main() {
    // Initialize database (uses APP_NAME env var for path)
    close, err := db.Init()
    if err != nil {
        log.Fatal(err)
    }
    defer close()

    ctx := context.Background()

    // Query single row
    row := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", 1)
    user, err := db.Scan[User](row)
    if err != nil {
        log.Fatal(err)
    }

    // Query multiple rows with iterator
    rows, err := db.QueryContext(ctx, "SELECT * FROM users")
    if err != nil {
        log.Fatal(err)
    }
    for user, err := range db.ScanAll[User](rows) {
        if err != nil {
            log.Fatal(err)
        }
        // use user
    }
}

API Overview

Initialization
close, err := db.Init()

Initializes the SQLite database using the APP_NAME environment variable to determine the database path. Returns a cleanup function to close the database connection.

Query Functions
// Query multiple rows
rows, err := db.QueryContext(ctx, query, args...)

// Query single row
row := db.QueryRowContext(ctx, query, args...)

// Execute without returning rows
result, err := db.ExecContext(ctx, query, args...)

// Begin transaction
tx, err := db.BeginTx(ctx, options)
Generic Scanning
// Scan single row into type T
value, err := db.Scan[T](row)

// Scan multiple rows into iterator of type T
for value, err := range db.ScanAll[T](rows) {
    // handle value
}

Works with:

  • Structs (maps columns to fields)
  • Scalar types (int, string, bool, etc.)
  • Pointer types for NULL handling
Column Mapping

Fields are mapped to database columns using:

  1. Explicit db struct tags: db:"column_name"
  2. Automatic snake_case conversion: UserNameuser_name

Examples

See the full example for comprehensive demonstrations including:

  • Struct scanning with NULL handling
  • Scalar value queries
  • Transactions
  • Various query patterns

Database Location

The database file is stored in a ./data/ directory, determined by the APP_NAME environment variable:

APP_NAME=myapp go run .

Creates/opens: ./data/myapp.db

SQLite Configuration

The library applies server-oriented SQLite best practices based on kerkour.com/sqlite-for-servers.

Read/Write Pool Split

Two separate connection pools are used:

  • Write pool (SetMaxOpenConns(1)): Serializes writes at the application level to prevent lock contention. Uses _txlock=immediate to acquire the write lock upfront in transactions.
  • Read pool (SetMaxOpenConns(max(4, NumCPU))): Scales concurrent readers with available CPU cores. WAL mode allows reads to proceed without blocking on writes.
PRAGMA Settings

Applied to both pools on initialization:

PRAGMA Value Purpose
journal_mode WAL Enables concurrent reads during writes
synchronous NORMAL Safe with WAL, better performance than FULL
cache_size -1000000 ~1 GB page cache (negative = KiB)
foreign_keys true Enforces foreign key constraints
busy_timeout 5000 Retries on lock contention for up to 5 seconds
temp_store memory Keeps temporary tables in RAM

On shutdown, PRAGMA wal_checkpoint(TRUNCATE) is called to fold the WAL back into the database file before closing.

License

MIT

Documentation

Overview

Package db provides a SQLite database abstraction compatible with database/sql.

This package offers a simple interface for SQLite operations using modernc.org/sqlite driver. It exposes stdlib-compatible methods (QueryContext, QueryRowContext, ExecContext, BeginTx) along with generic scanning functions (Scan, ScanAll) for mapping rows to Go types.

Key features:

  • stdlib-compatible API: QueryContext, QueryRowContext, ExecContext, BeginTx
  • Generic Scan[T] for single row mapping
  • Generic ScanAll[T] for multiple rows with iterator pattern
  • Hybrid column mapping: explicit db tags or automatic snake_case conversion
  • Support for SELECT * queries with any column order (ScanAll only)
  • Iterator-based results with iter.Seq2[T, error] for proper error handling
  • Database initialization from APP_NAME environment variable

Example usage:

close, err := db.Init()
if err != nil {
	log.Fatal(err)
}
defer close()

// Single row query
row := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", 1)
user, err := db.Scan[User](row)
if err != nil {
	// handle error
}

// Multiple rows query
rows, err := db.QueryContext(ctx, "SELECT * FROM users ORDER BY id")
if err != nil {
	// handle error
}
for user, err := range db.ScanAll[User](rows) {
	if err != nil {
		// handle error
	}
	// use user
}

// Transaction
tx, err := db.BeginTx(ctx, nil)
if err != nil {
	// handle error
}
defer tx.Rollback()
// ... use tx
tx.Commit()

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BeginTx

func BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)

BeginTx starts a transaction. The default isolation level is dependent on the driver.

func ExecContext

func ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)

ExecContext executes a query without returning any rows. The args are for any placeholder parameters in the query.

func Init

func Init() (func() error, error)

func QueryContext

func QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)

QueryContext executes a query that returns rows, typically a SELECT. The args are for any placeholder parameters in the query.

func QueryRowContext

func QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row

QueryRowContext executes a query that is expected to return at most one row. The args are for any placeholder parameters in the query.

func Scan

func Scan[T any](row *sql.Row) (T, error)

Scan scans a single row into a value of type T. For scalar types (string, int, etc.), it scans directly. For struct types, it scans fields in declaration order with NULL handling. Pointer fields receive nil for NULL values, non-pointer primitives receive zero values.

func ScanAll

func ScanAll[T any](rows *sql.Rows) iter.Seq2[T, error]

ScanAll scans multiple rows into an iterator of type T. For scalar types, each row must have exactly one column. For struct types, it maps columns to fields using db tags or snake_case conversion. Supports flexible column ordering and NULL handling like the original Query[T].

Types

This section is empty.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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