sqlitebp

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2025 License: MIT Imports: 9 Imported by: 0

README

sqlitebp - SQLite Best Practices for Go

Go Reference Coverage Go Version

sqlitebp provides an opinionated, production-ready configuration for SQLite databases in Go applications. It implements SQLite best practices with sensible defaults focused on safety, performance, and reliability.

Installation

go get github.com/jacob2161/sqlitebp

Examples

Open for read/write (create if required)
package main

import (
    "fmt"
    "log"

    "github.com/jacob2161/sqlitebp"
)

func main() {
    // Creates or opens the database with best-practice defaults
    // (WAL, foreign keys, busy timeout, NORMAL synchronous, private cache, etc.)
    db, err := sqlitebp.OpenReadWriteCreate("app.db")
    if err != nil { log.Fatal(err) }
    defer db.Close()

    // Create a table (STRICT for stronger type enforcement)
    if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL) STRICT`); err != nil {
        log.Fatal(err)
    }
    // Insert a row
    if _, err := db.Exec(`INSERT INTO users (name) VALUES (?)`, "Alice"); err != nil {
        log.Fatal(err)
    }
    // Query a value
    var count int
    if err := db.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&count); err != nil {
        log.Fatal(err)
    }
    fmt.Println("User rows:", count)
}
Open existing read/write
db, err := sqlitebp.OpenReadWrite("app.db")
if err != nil {
    log.Fatal(err)
}
// e.g. read previously inserted rows
var n int
if err := db.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&n); err != nil {
    log.Fatal(err)
}
Read-only
db, err := sqlitebp.OpenReadOnly("app.db")
if err != nil {
    log.Fatal(err)
}
var n int
if err := db.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&n); err != nil {
    log.Fatal(err)
}
With Options
// Increase busy timeout (seconds), enlarge cache, enforce FULL synchronous
db, err := sqlitebp.OpenReadWriteCreate("app.db",
    sqlitebp.WithBusyTimeoutSeconds(30),
    sqlitebp.WithCacheSizeMiB(64),
    sqlitebp.WithSynchronous("FULL"),
)
if err != nil {
    log.Fatal(err)
}
Adjust Journaling Mode
// Use DELETE journal instead of WAL
db, err := sqlitebp.OpenReadWriteCreate("app.db",
    sqlitebp.WithJournalMode("DELETE"),
)
if err != nil {
    log.Fatal(err)
}
Override temp_store
// Force temporary tables to disk instead of memory (trade performance for lower RAM)
db, err := sqlitebp.OpenReadWriteCreate("app.db",
    sqlitebp.WithTempStore("FILE"),
)
if err != nil {
    log.Fatal(err)
}
Disable PRAGMA optimize
// Disable automatic PRAGMA optimize on new connections
// (Enabled by default unless explicitly disabled)
db, err := sqlitebp.OpenReadWriteCreate("app.db",
    sqlitebp.WithOptimize(false),
)
if err != nil {
    log.Fatal(err)
}

(Shared cache is intentionally not supported; private cache is enforced to avoid shared-cache pitfalls.)

Features & Best Practices

Default Configuration

The package applies these SQLite best practices automatically:

  1. WAL Mode (_journal_mode=WAL) except in read-only mode (journal not forced when read-only)
  2. Foreign Keys Enabled (_foreign_keys=true)
  3. Busy Timeout (_busy_timeout=10000 ms)
  4. Private Cache enforced (cache=private) - not user configurable
  5. Synchronous NORMAL (_synchronous=NORMAL)
  6. Page Cache 32 MiB (_cache_size=-32768 KB)
  7. Smart Connection Pool (2-8 connections based on GOMAXPROCS)
  8. PRAGMA optimize on each connection (disable via WithOptimize(false))
  9. Temp Storage in Memory by default (PRAGMA temp_store=MEMORY) - overridable via WithTempStore

Platform Support

Optimized and tested for Linux. Other platforms may work but are not a focus.

Memory Considerations

  • Base page cache: ~32 MiB (configurable via WithCacheSizeMiB)
  • Temp tables & sorts: additional RAM depending on workload (switch to FILE via WithTempStore("FILE") if needed)

Connection Modes

OpenReadWriteCreate
  • Creates database if it doesn't exist
  • Full read/write, all optimizations
OpenReadWrite
  • Database must exist
  • Full read/write
OpenReadOnly
  • Database must exist
  • No writes
  • Existing journal mode respected (WAL not forced)
  • Other optimizations still applied (foreign keys, busy timeout unaffected)

Testing

Run tests:

go test -v

License

MIT. See LICENSE

Contributing

PRs welcome that improve safety, correctness, or performance.

Documentation

Overview

Package sqlitebp provides opinionated best practices for opening SQLite databases with sensible defaults for connection pooling, pragmas, and configuration options.

Example
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/jacob2161/sqlitebp"
)

func main() {
	os.Remove("example.db")
	os.Remove("example.db-shm")
	os.Remove("example.db-wal")
	db, err := sqlitebp.OpenReadWriteCreate("example.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
	defer func() { os.Remove("example.db"); os.Remove("example.db-shm"); os.Remove("example.db-wal") }()
	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL) STRICT`)
	if err != nil {
		log.Fatal(err)
	}
	res, err := db.Exec("INSERT INTO users (name) VALUES (?)", "Alice")
	if err != nil {
		log.Fatal(err)
	}
	id, _ := res.LastInsertId()
	fmt.Printf("Inserted user with ID: %d\n", id)
}
Example (CustomOptions)
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/jacob2161/sqlitebp"
)

func main() {
	os.Remove("custom.db")
	os.Remove("custom.db-shm")
	os.Remove("custom.db-wal")
	db, err := sqlitebp.OpenReadWriteCreate("custom.db",
		sqlitebp.WithBusyTimeoutSeconds(30),
		sqlitebp.WithCacheSizeMiB(64),
		sqlitebp.WithSynchronous("FULL"),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
	defer func() { os.Remove("custom.db"); os.Remove("custom.db-shm"); os.Remove("custom.db-wal") }()
	fmt.Println("Database opened with custom options")
}
Example (ReadOnly)
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/jacob2161/sqlitebp"
)

func main() {
	os.Remove("readonly.db")
	os.Remove("readonly.db-shm")
	os.Remove("readonly.db-wal")
	// Ensure exists
	db, err := sqlitebp.OpenReadWriteCreate("readonly.db")
	if err != nil {
		log.Fatal(err)
	}
	db.Close()
	ro, err := sqlitebp.OpenReadOnly("readonly.db")
	if err != nil {
		log.Fatal(err)
	}
	defer ro.Close()
	var count int
	if err := ro.QueryRow("SELECT COUNT(*) FROM sqlite_master").Scan(&count); err != nil {
		log.Fatal(err)
	}
	if _, err := ro.Exec("CREATE TABLE test (id INTEGER) STRICT"); err != nil {
		fmt.Println("Write failed as expected in read-only mode")
	}
	defer func() { os.Remove("readonly.db"); os.Remove("readonly.db-shm"); os.Remove("readonly.db-wal") }()
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrEmptyFilename indicates an empty filename was supplied.
	ErrEmptyFilename = errors.New("sqlitebp: filename cannot be empty")
	// ErrInvalidMode indicates an internal invalid mode value.
	ErrInvalidMode = errors.New("sqlitebp: invalid mode")
	// ErrOpenFailed indicates the database could not be opened.
	ErrOpenFailed = errors.New("sqlitebp: open failed")
	// ErrPragmaExec indicates a PRAGMA failed during connection initialization.
	ErrPragmaExec = errors.New("sqlitebp: pragma execution failed")
	// ErrPingFailed indicates ping validation failed after opening.
	ErrPingFailed = errors.New("sqlitebp: ping failed")
	// ErrInvalidConfigOption indicates an invalid configuration option was supplied.
	ErrInvalidConfigOption = errors.New("sqlitebp: invalid config option")
)

Functions

func OpenReadOnly

func OpenReadOnly(filename string, opts ...Option) (*sql.DB, error)

OpenReadOnly opens an existing database in read-only mode (journal mode not forced; no writes).

func OpenReadWrite

func OpenReadWrite(filename string, opts ...Option) (*sql.DB, error)

OpenReadWrite opens an existing database with read/write access (must exist).

func OpenReadWriteCreate

func OpenReadWriteCreate(filename string, opts ...Option) (*sql.DB, error)

OpenReadWriteCreate opens or creates a database with full read/write access.

Types

type Option

type Option func(*openConfig) error

Option configures database parameters prior to opening.

func WithBusyTimeoutSeconds

func WithBusyTimeoutSeconds(sec int) Option

WithBusyTimeoutSeconds sets the busy timeout (seconds >=0). Translated to _busy_timeout (ms).

func WithCacheSizeMiB

func WithCacheSizeMiB(mib int) Option

WithCacheSizeMiB sets the page cache size in MiB (negative KiB form).

func WithCaseSensitiveLike

func WithCaseSensitiveLike(enabled bool) Option

WithCaseSensitiveLike toggles case_sensitive_like pragma.

func WithForeignKeys

func WithForeignKeys(enabled bool) Option

WithForeignKeys enables or disables foreign key enforcement.

func WithJournalMode

func WithJournalMode(mode string) Option

WithJournalMode sets journal mode (ignored in read-only opens where we do not force WAL).

func WithMMapSize

func WithMMapSize(bytes int64) Option

WithMMapSize sets the mmap size in bytes (0 disables memory mapping growth beyond default). Applies via DSN.

func WithOptimize

func WithOptimize(enabled bool) Option

WithOptimize enables or disables running PRAGMA optimize on each new connection (default enabled).

func WithRecursiveTriggers

func WithRecursiveTriggers(enabled bool) Option

WithRecursiveTriggers toggles recursive_triggers.

func WithSecureDelete

func WithSecureDelete(mode string) Option

WithSecureDelete sets secure_delete mode (FAST, ON, OFF).

func WithSynchronous

func WithSynchronous(level string) Option

WithSynchronous sets the synchronous level.

func WithTempStore

func WithTempStore(store string) Option

WithTempStore overrides temp_store using a per-connection PRAGMA (DEFAULT, FILE, MEMORY). This cannot be reliably set via DSN driver underscore parameter; we apply it through ConnectHook.

Jump to

Keyboard shortcuts

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