sqlite

package module
v0.2.12 Latest Latest
Warning

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

Go to latest
Published: Apr 1, 2026 License: MIT Imports: 18 Imported by: 2

README

░██████╗░██████╗░██╗░░░░░██╗████████╗███████╗
██╔════╝██╔═══██╗██║░░░░░██║╚══██╔══╝██╔════╝
╚█████╗░██║██╗██║██║░░░░░██║░░░██║░░░█████╗░░
░╚═══██╗╚██████╔╝██║░░░░░██║░░░██║░░░██╔══╝░░
██████╔╝░╚═██╔═╝░███████╗██║░░░██║░░░███████╗
╚═════╝░░░░╚═╝░░░╚══════╝╚═╝░░░╚═╝░░░╚══════╝

Go Reference Go Report Card

sqlite wraps zombiezen.com/go/sqlite with connection pooling, automatic parameter binding, migrations, and geo queries.

Installation

go get ella.to/sqlite

Opening a Database

db, err := sqlite.New(ctx,
    sqlite.WithFile("app.db"),
    sqlite.WithPoolSize(10),
)
defer db.Close()

Every new connection gets these PRAGMAs applied automatically:

  • foreign_keys = ON
  • journal_mode = WAL
  • cache_size = -2000 (2 MB)
  • temp_store = MEMORY

In-Memory Database

db, err := sqlite.New(ctx, sqlite.WithMemory())

Custom Connection String

db, err := sqlite.New(ctx, sqlite.WithStringConn("file:app.db?mode=ro"))

Connection Preparation

Run setup logic on every new connection in the pool (e.g. loading extensions or registering custom functions):

db, err := sqlite.New(ctx,
    sqlite.WithFile("app.db"),
    sqlite.WithConnPrepareFunc(func(conn *sqlite.Conn) error {
        return conn.ExecScript(`PRAGMA busy_timeout = 5000;`)
    }),
    sqlite.WithFunctions(map[string]*sqlite.FunctionImpl{
        "my_upper": {
            NArgs:         1,
            Deterministic: true,
            Scalar: func(ctx *sqlite.Context, args []sqlite.Value) {
                ctx.ResultText(strings.ToUpper(args[0].Text()))
            },
        },
    }),
)

Running Queries

Get a Connection

conn, err := db.Conn(ctx)
defer conn.Done() // always return to pool

Prepare and Execute

Prepare auto-binds parameters by position. It understands Go types and maps them to SQLite types:

stmt, err := conn.Prepare(ctx, "SELECT * FROM users WHERE id = ? AND active = ?", userID, true)
Go Type SQLite Type
string, fmt.Stringer TEXT
int, int64, etc. INTEGER
float32, float64 FLOAT
bool INTEGER (0/1)
[]byte, json.RawMessage BLOB
time.Time INTEGER (Unix timestamp)
nil NULL
maps, slices, structs TEXT (JSON-encoded)

Using the Exec Helper

For one-off operations where you don't need to manage the connection yourself:

err := db.Exec(ctx, func(ctx context.Context, conn *sqlite.Conn) error {
    stmt, err := conn.Prepare(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", name, email)
    if err != nil {
        return err
    }
    _, err = stmt.Step()
    return err
})

Transactions

Use Save with a deferred call for automatic commit/rollback based on error:

err := db.Exec(ctx, func(ctx context.Context, conn *sqlite.Conn) (err error) {
    defer conn.Save(&err)

    stmt, _ := conn.Prepare(ctx, "INSERT INTO orders (user_id, total) VALUES (?, ?)", userID, total)
    stmt.Step()

    stmt, _ = conn.Prepare(ctx, "UPDATE users SET order_count = order_count + 1 WHERE id = ?", userID)
    stmt.Step()

    return nil // commits on nil, rolls back on error
})

Running SQL Scripts

// Execute a multi-statement string
err := sqlite.RunScript(ctx, db, `
    CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, name TEXT);
    CREATE INDEX IF NOT EXISTS idx_users_name ON users(name);
`)

// Execute all .sql files from a directory (sorted alphabetically)
err := sqlite.RunScriptFiles(ctx, db, "./sql/setup/")

Migrations

Apply SQL migration files from an embedded filesystem. Already-applied migrations are tracked in a migrations_sqlite table, so only new files run on each call.

//go:embed migrations/*.sql
var migrationsFS embed.FS

err := sqlite.Migration(ctx, db, migrationsFS, "migrations")

Migration files are applied in sorted order. Name them with a numeric prefix to control ordering:

migrations/
  001_create_users.sql
  002_add_email_index.sql
  003_create_orders.sql

Reading Columns

Helper functions for extracting typed values from query results:

// Unix timestamp -> time.Time (UTC)
created := sqlite.LoadTime(stmt, "created_at")

// Integer -> bool
active := sqlite.LoadBool(stmt, "is_active")

// JSON column -> Go types
attrs, err := sqlite.LoadJsonMap[string](stmt, "attributes")
tags, err := sqlite.LoadJsonArray[string](stmt, "tags")
profile, err := sqlite.LoadJsonStruct[UserProfile](stmt, "profile")

Placeholders

Generate placeholder strings for building dynamic queries:

// Single row: "?, ?, ?"
sqlite.Placeholders(3)

// Multiple rows: "(?, ?), (?, ?), (?, ?)"
sqlite.GroupPlaceholders(3, 2)

Geo Queries

Built-in support for bounding-box distance queries using the haversine formula. Useful for "find things near this point" queries.

// WHERE clause that filters to a bounding box around the point
where := sqlite.CreateCondSQL(lat, lon, distanceInKm)

// SELECT expression that computes squared distance from the point
dist := sqlite.CreateDistanceSQL(lat, lon)

query := fmt.Sprintf(
    "SELECT *, %s AS dist FROM locations WHERE %s ORDER BY dist LIMIT 20",
    dist, where,
)

The bounding box uses a 1.1x safety margin, so you get slightly more results than the exact radius — good for a first pass before doing precise distance filtering in your application.

Debugging

Format a query with its parameters for logging:

sql := sqlite.ShowSql("SELECT * FROM users WHERE name = ? AND age > ?", "Alice", 30)
// SELECT * FROM users WHERE name = 'Alice' AND age > 30

Error Sentinels

sqlite.ErrNotFound    // row not found
sqlite.ErrPrepareSQL  // failed to prepare statement
sqlite.ErrExecSQL     // failed to execute statement
sqlite.ErrUnknownType // unsupported Go type for parameter binding

License

MIT — see LICENSE.md for details.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFound    = errors.New("database row not found")
	ErrPrepareSQL  = errors.New("database failed to prepare sql")
	ErrExecSQL     = errors.New("database failed to exec sql")
	ErrUnknownType = errors.New("database failed to prepare sql because of unknown type")
)
View Source
var IntegerValue = sqlite.IntegerValue

Functions

func CreateCondSQL

func CreateCondSQL(latitude, longitude, distance float64) string

func CreateDistanceSQL

func CreateDistanceSQL(latitude, longitude float64) string

func GroupPlaceholders

func GroupPlaceholders(numRows, numCols int) string

(?, ?), (?, ?), (?, ?)

func GroupPlaceholdersStringBuilder

func GroupPlaceholdersStringBuilder(numRows, numCols int, sb *strings.Builder)

func LoadBool

func LoadBool(stmt *Stmt, key string) bool

func LoadJsonArray

func LoadJsonArray[T any](stmt *Stmt, col string) ([]T, error)

func LoadJsonMap

func LoadJsonMap[T any](stmt *Stmt, col string) (map[string]T, error)

func LoadJsonStruct added in v0.2.7

func LoadJsonStruct[T any](stmt *Stmt, col string) (*T, error)

func LoadTime

func LoadTime(stmt *Stmt, key string) time.Time

func Migration

func Migration(ctx context.Context, db *Database, fs ReadDirFileFS, dir string) error

migration calls read each sql files in the migration directory and applies it to the database. It will create a table called migrations_sqlite to keep track of the files that have been applied.

Use this function to apply migrations to the database at the start of your application. Make sure each file name is unique and the use either a timestamp or counter to make sure the files are applied in the correct order.

func Placeholders

func Placeholders(count int) string

Placeholders returns a string of ? separated by commas

func RunScript

func RunScript(ctx context.Context, db *Database, sql string) error

func RunScriptFiles

func RunScriptFiles(ctx context.Context, db *Database, path string) error

func ShowSql

func ShowSql(sql string, args ...any) string

func Sql

func Sql(sql string, values ...any) string

Types

type AggregateFunction

type AggregateFunction = sqlite.AggregateFunction

Reason behind this is that I don't want to import two packages that starts with sqlite into my project. I can use the type alias in my project

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

func (*Conn) Done added in v0.0.9

func (c *Conn) Done()

Done returns the connection back to the pool

func (*Conn) ExecScript

func (c *Conn) ExecScript(sql string) error

Use this function to execute a script that contains multiple SQL statements

func (*Conn) Prepare

func (c *Conn) Prepare(ctx context.Context, sql string, values ...any) (*Stmt, error)

func (*Conn) Save

func (c *Conn) Save(err *error)

When your try to use transaction in a nice way, you can use the following at the beginning of your code:

defer conn.Save(&err)

type ConnPrepareFunc added in v0.1.0

type ConnPrepareFunc func(*Conn) error

type Context

type Context = sqlite.Context

Reason behind this is that I don't want to import two packages that starts with sqlite into my project. I can use the type alias in my project

type Database

type Database struct {
	// contains filtered or unexported fields
}

Database struct which holds pool of connection

func New

func New(ctx context.Context, opts ...OptionFunc) (*Database, error)

New creates a sqlite database

func (*Database) Close

func (db *Database) Close() error

Close closes all the connections in the pool and returns error if any connection fails to close NOTE: make sure to call this function at the end of your application

func (*Database) Conn

func (db *Database) Conn(ctx context.Context) (*Conn, error)

Conn returns one connection from connection pool NOTE: make sure to call Done() to put the connection back to the pool usually right after this call, you should call defer conn.Done()

func (*Database) Exec added in v0.2.4

func (db *Database) Exec(ctx context.Context, fn func(ctx context.Context, conn *Conn) error) error

type FunctionImpl

type FunctionImpl = sqlite.FunctionImpl

Reason behind this is that I don't want to import two packages that starts with sqlite into my project. I can use the type alias in my project

type OptionFunc

type OptionFunc func(context.Context, *Database) error

func WithConnPrepareFunc added in v0.1.0

func WithConnPrepareFunc(fn ConnPrepareFunc) OptionFunc

func WithFile

func WithFile(path string) OptionFunc

func WithFunctions

func WithFunctions(fns map[string]*FunctionImpl) OptionFunc

func WithMemory

func WithMemory() OptionFunc

func WithPoolSize

func WithPoolSize(size int) OptionFunc

func WithStringConn

func WithStringConn(stringConn string) OptionFunc

type ReadDirFileFS

type ReadDirFileFS interface {
	fs.ReadDirFS
	fs.ReadFileFS
}

type Stmt

type Stmt = sqlite.Stmt

Reason behind this is that I don't want to import two packages that starts with sqlite into my project. I can use the type alias in my project

type Value

type Value = sqlite.Value

Reason behind this is that I don't want to import two packages that starts with sqlite into my project. I can use the type alias in my project

Jump to

Keyboard shortcuts

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