litesql

package module
v1.3.4 Latest Latest
Warning

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

Go to latest
Published: Jun 25, 2026 License: BSD-3-Clause Imports: 13 Imported by: 0

README

litesql

Go Reference License Build

litesql is a Go library for working with SQLite3, providing reasonable defaults and an easy-to-use API for reliable and performant database access.

Getting Started

The litesql package can be added to a Go project with go get.

go get cattlecloud.net/go/litesql@latest
import "cattlecloud.net/go/litesql"
Examples
Opening a SQLite database

The litesql.TypicalConfiguration contains reasonable defaults for many general applications such as webapps. You may wish to use it as a reference and fine-tune parameters for each use case.

db, err := litesql.Open("/path/to/file", litesql.TypicalConfiguration)
// ...
defer db.Close()
Starting SQLite transactions

The *LiteDB returned by Open provides StartRead and StartWrite for starting a read or write transaction. They make use of the ReadConsistency and WriteConsistency package values to indicate isolation levels. A write transaction must be ended with a call to Commit.

// read transaction
tx, done, xerr := db.StartRead(ctx)
if xerr != nil {
    return xerr
}
defer done()

// ... use tx to execute queries ...
// write transaction
tx, done, xerr = db.StartWrite(ctx)
if xerr != nil {
    return xerr
}
defer done()

// ... use tx to execute statements ...

// commit the write transaction
return tx.Commit()
Query rows

Convenience functions QueryRow and QueryRows exist at the package level for abstracting much of the boiler-plate code for reading rows. By supplying a ScanFunc, you can fetch row(s) without managing most of the query logic.

func example(id int) ([]*record, error) {
  tx, done, xerr := db.StartRead(ctx)
  if xerr != nil {
    return nil, xerr
  }
  defer done()

  const statement = `select * from mytable where id > ?`

  f := func(sf litesql.ScanFunc) (*record, error) {
    r := new(record)
    err := sf(
      // &r.field1
      // &r.field2
      // ...
    )
    return r, err
  }

  return litesql.QueryRows(ctx, tx, f, statement, id)
}
Execute statement

The ExecID and Exec statements are for write transactions. The Exec statement needs to know how many rows to expect to be modified, returning an error if expectations are not met. There are special package constants for indicating certain special cases. The ExecID method expects one row to be changed, and will return the ROWID of the affected (or added) row.

ExpectAnything   // do not enforce any expecation on number of rows changed
ExpectNonZero    // at least one row must be changed
ExpectOneOrZero  // exactly 0 or 1 row must be changed, useful for upserts
ExpectOne        // exactly 1 row must be changed
ExpectNone       // exactly 0 rows must be changed

A simple update example.

func example(id int, value string) error {
  tx, done, xerr := db.StartWrite(ctx)
  if xerr != nil {
    return xerr
  }
  defer done()

  const statement = `update mytable set v = ? where id = ?`

  if err := db.Exec(ctx, tx, litesql.ExpectOneOrZero, statement, value, id); err != nil {
    return err
  }

  return tx.Commit()
}
Show pragma values

It is often helpful to dump the database pragma values on startup. This can be done using the Pragmas method, which returns a map[string]string of most common SQLite pragma configuration values.

m, _ := db.Pragmas(ctx)

for k, v := range m {
  fmt.Println("pragma", k, "value", v)
}
Creating a snapshot

The Snapshot method creates a point-in-time copy of the database, useful for backups or creating read-only copies for sharing. Snapshots are copied page-by-page using SQLite's backup API, with configurable Step and Gap options to control concurrency with writers.

err := db.Snapshot(&litesql.SnapshotOptions{
    Directory:  "/path/to/snapshots",        // where to write the snapshot
    Retention:  3,                           // keep last 3 snapshots
    Step:       100,                         // copy 100 pages per step
    Gap:        1 * time.Millisecond,       // wait between steps
    Progress: func(pages, remaining int) {
        fmt.Printf("progress: %d/%d pages\n", pages-remaining, pages)
    },
})

The snapshot files are named snapshot-<timestamp>.db in the specified directory, and older files are automatically cleaned up according to the Retention policy.

Sanitizing FTS5 queries

SQLite FTS5 uses special control characters (*, :, ^, ", etc.) that can cause query errors or allow malicious query injection. Use SanitizeFTS5 to strip these characters before passing user input to an FTS5 query.

query := "hello world*"                  // prefix-operator performance implications
sanitized := litesql.SanitizeFTS5(query) // removes fts5 control characters
fmt.Println(sanitized)                   // "hello world"
Database Maintenance

A *LiteDB provides three methods for database maintenance: Optimize, Analyze, and Vacuum. Each serves a different purpose and should be run at different intervals.

Optimize updates internal schema metadata used by the query planner. It should be called periodically (e.g., weekly or after bulk inserts/deletes) to maintain optimal query performance. The operation is fast and non-blocking.

err := db.Optimize(ctx)

Analyze gathers statistics about table and index data distribution. These statistics help the query planner choose efficient execution plans. Run after significant data changes (e.g., large inserts, deletes, or bulk updates) to ensure the planner has accurate information. Fast and non-blocking.

err := db.Analyze(ctx)

Vacuum rebuilds the database file, reclaiming unused space and defragmenting data. It should be run sparingly (e.g., monthly or after many deletions) as it requires exclusive database access and can be slow for large databases. Improves performance by reducing file size and greatly improving sequential I/O efficiency.

err := db.Vacuum(ctx)

A common maintenance schedule is to run Optimize daily or weekly, Analyze after large data changes, and Vacuum monthly or after a large number of delete operations.

License

The cattlecloud.net/go/litesql module is opensource under the BSD-3-Clause license.

Documentation

Overview

Package litesql implements a SQLite3 interface with reasonable defaults, making interacting with sqlite3 databases easy, reliable, and performant in Go programs.

Index

Constants

View Source
const (
	// ExpectAnything indicates there is no expectation for the number of
	// rows that will be updated as a result of executing a statement.
	ExpectAnything = -(iota + 1)

	// ExpectNonZero indicates the expectation for the number of rows that will
	// be updated is non-zero.
	ExpectNonZero

	// ExpectOneOrZero indicates the expectation for the number of rows that
	// will be updated is exactly 0 or 1 (e.g. insert or ignore)
	ExpectOneOrZero
)
View Source
const (
	// ExpectNone indicates no rows should be updated as a result of executing
	// a statement.
	ExpectNone = 0

	// ExpectOne indicates exactly one row should be updated as a result of
	// executing a statement.
	ExpectOne = 1
)
View Source
const (
	// ExecFailure is a sentinel value used when an Exec statement did not
	// complete correctly and we have no resulting row ID.
	ExecFailure = -1

	// TxFailure is a sentinel value used when the creation of a transaction
	// fails and the query cannot continue, and we have no row ID.
	TxFailure = -2
)

Variables

View Source
var (
	// ReadConsistency provides options for general use transactional reads.
	//
	// Do not modify unless you are fully aware of the consequences.
	ReadConsistency = &sql.TxOptions{
		ReadOnly:  true,
		Isolation: sql.LevelReadCommitted,
	}

	// WriteConsistency provides options for general use transactional writes.
	//
	// Do not modify unless you are fully aware of the consequences.
	WriteConsistency = &sql.TxOptions{
		ReadOnly:  false,
		Isolation: sql.LevelSerializable,
	}
)
View Source
var TypicalConfiguration = &Configuration{
	Mode:               "rwc",
	Encoding:           "utf8",
	BusyTimeout:        5000,
	TransactionLock:    "immediate",
	ForeignKeys:        true,
	JournalMode:        "WAL",
	CacheSize:          -65536,
	AutoVacuum:         "incremental",
	Synchronous:        "normal",
	MemoryMapSize:      67108864,
	MaxConnectionsOpen: 4,
}

TypicalConfiguration should be suitable for use by most applications, such as web servers, api servers, and other general purpose scenarios. In particular it sets these parameter values:

  • encoding: utf8
  • journal_mode: WAL
  • foreign_keys: true
  • transaction_lock: immediate
  • auto_vacuum: incremental
  • synchronous: normal
  • cache_size: 64 megabytes
  • mmap_size: 64 megabytes

Do not modify.

Functions

func QueryRow

func QueryRow[T any](ctx scope.C, tx *sql.Tx, scan func(ScanFunc) (T, error), stmt string, args ...any) (T, error)

QueryRow uses the given transaction tx and the scan function to extract a single row from the database, using the given stmnt query with args.

The scan function is provided by the caller for custom extraction of column values into some type T.

func QueryRows

func QueryRows[T any](ctx scope.C, tx *sql.Tx, scan func(ScanFunc) (T, error), stmt string, args ...any) ([]T, error)

QueryRows uses the given transaction tx and the scan function to extract a set of rows from the database, using the given stmnt query with args.

The scan function is provided by the caller for custom extraction of column values into some type T.

func SanitizeFTS5 added in v1.3.0

func SanitizeFTS5(query string) string

SanitizeFTS5 removes SQLite FTS5 control characters (*, :, ^, ", etc.) by retaining only alphanumeric characters and basic whitespace.

Types

type CloseFunc

type CloseFunc func()

CloseFunc should always be called before a transaction goes out of scope, even if the transaction has been committed (which turns into no-op).

type Configuration

type Configuration struct {
	// Mode configures the read/write/create capability.
	//
	// Use "rwc" for most use cases.
	Mode string

	// Encoding configures the encoding pragma. Value must be one of:
	//
	//  - UTF-8
	//  - UTF-16
	//  - UTF-16le
	//  - UTF-16be
	//
	// https://sqlite.org/pragma.html#pragma_encoding
	Encoding string

	// BusyTimeout configures the busy_timeout pragma. Value in milliseconds.
	//
	// https://sqlite.org/pragma.html#pragma_busy_timeout
	BusyTimeout int

	// TransactionLock configures the locking_mode pragma. Value must be one of:
	//
	//  - NORMAL
	//  - EXCLUSIVE
	//
	// https://sqlite.org/pragma.html#pragma_locking_mode
	TransactionLock string

	// ForeighKeys configures the foreign_keys pragma. Value must be one of:
	//
	//  - true
	//  - false
	//
	// https://sqlite.org/pragma.html#pragma_foreign_keys
	ForeignKeys bool

	// JournalMode configures the journal_mode pragma. Value must be one of:
	//
	//  - DELETE
	//  - TRUNCATE
	//  - PERSIST
	//  - MEMORY
	//  - WAL
	//  - OFF
	//
	// https://sqlite.org/pragma.html#pragma_journal_mode
	JournalMode string

	// CacheSize configures the cache_size pragma.
	//
	// When set to a positive value, the size is interpreted in pages.
	// When set to a negative value, the size is interpreted in kibibytes.
	//
	// e.g. 100 => 4kb page size * 100 => 400,000 bytes of memory
	// e.g. -2000 => 2048000 bytes of memory
	//
	// https://sqlite.org/pragma.html#pragma_cache_size
	CacheSize int

	// AutoVacuum configures the auto_vacuum pragma. Value must be one of:
	//
	//  - NONE
	//  - FULL
	//  - INCREMENTAL
	//
	// https://sqlite.org/pragma.html#pragma_auto_vacuum
	AutoVacuum string

	// Synchronous configures the synchronous pragma. Value must be one of:
	//
	//  - OFF
	//  - NORMAL
	//  - FULL
	//  - EXTRA
	//
	// https://sqlite.org/pragma.html#pragma_synchronous
	Synchronous string

	// MemoryMapSize configures the mmap_size pragma.
	//
	// Using mmap bypasses the kernel memory buffer, reducing the amount of
	// memory copying and syscall overhead. Understand the risks before using.
	//
	// Not used for in-memory databases.
	//
	// https://sqlite.org/pragma.html#pragma_mmap_size
	MemoryMapSize int

	// MaxConnectionsOpen configures the maximum number of concurrent users
	// of the database.
	MaxConnectionsOpen int
}

Configuration is the set of PRAGMA values and/or connection parameters used to configure the sqlite database.

Most general purpose applications such as web servers and api servers may want to start with the default values provided in TypicalConfiguration.

type ID

type ID int

ID represents a unique ROW ID of a table.

Most often, this is coming from an autoincrement index.

A common pattern is to create a new type based on this type, so as to leverage the type system ensuring ID values are not co-mingled.

func (ID) String added in v0.1.2

func (id ID) String() string

type LiteDB

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

LiteDB is a library around an sqlite3 database providing reasonable default values and an easy-to-use set of APIs for efficient and performant access.

func Open

func Open(filename string, config *Configuration) (*LiteDB, error)

Open the database of the given filename, using config to tune the PRAGMA values and connection string parameters.

func (*LiteDB) Analyze added in v1.3.1

func (ldb *LiteDB) Analyze(ctx scope.C) error

Analyze runs SQLite's ANALYZE command, which gathers statistics about table and index data distribution. These statistics help the query planner choose efficient execution plans. Run after significant data changes (e.g., large inserts, deletes, or bulk updates) to ensure the planner has accurate information. Fast and non-blocking.

func (*LiteDB) Close

func (ldb *LiteDB) Close() error

Close the underlying database.

func (*LiteDB) Exec

func (ldb *LiteDB) Exec(ctx scope.C, tx *sql.Tx, expectation int, stmt string, args ...any) error

Exec executes the given sql query statement with args, and compares the number of rows affected with the given expectation. An error is returned if the number of rows does not match the given expectation. The constant values ExpectAnything, ExpectNonZero, and ExpectOneOrZero can be used for more complex, but common expected behaviors.

func (*LiteDB) ExecID

func (ldb *LiteDB) ExecID(ctx scope.C, tx *sql.Tx, stmt string, args ...any) (ID, error)

ExecID executes the given sql query statement with args, and returns the resulting row id. The query must be intended to insert/modify exactly one row.

func (*LiteDB) Optimize added in v1.3.1

func (ldb *LiteDB) Optimize(ctx scope.C) error

Optimize runs SQLite's PRAGMA optimize command, which updates internal schema metadata used by the query planner. It should be called periodically (e.g., weekly or after bulk inserts/deletes) to maintain optimal query performance. The operation is fast and non-blocking.

func (*LiteDB) Pragmas

func (ldb *LiteDB) Pragmas(ctx scope.C) (map[string]string, error)

Pragmas will query the database for the common set of PRAGMA values, so that one may look at them in all their magnificent glory.

func (*LiteDB) QueryRow

func (ldb *LiteDB) QueryRow(ctx scope.C, tx *sql.Tx, stmt string, args ...any) *sql.Row

QueryRow executes the given sql query statement with the expectation of returning exactly one row.

func (*LiteDB) QueryRows

func (ldb *LiteDB) QueryRows(ctx scope.C, tx *sql.Tx, stmt string, args ...any) (*sql.Rows, CloseFunc, error)

QueryRows executes the given sql query statement with the expectation of returning any number of rows.

Must call the returned CloseFunc when finished; otherwise a connection will be consumed and not returned to the connection pool, causing future operations to hang indefinitely.

func (*LiteDB) Snapshot added in v1.2.0

func (ldb *LiteDB) Snapshot(ctx scope.C, opts *SnapshotOptions) error

Snapshot creates a point-in-time snapshot of the database, copying all pages to a new database file in the specified directory.

The snapshot is performed by acquiring a connection to the database and using SQLite's backup API to copy pages to the new database. The Progress callback (if provided) is invoked after each step interval with the total number of pages and the number of pages remaining to be copied.

After the snapshot completes, older snapshot files are automatically cleaned up according to the Retention policy.

Example:

ctx := context.Background()
err := db.Snapshot(ctx, &SnapshotOptions{
    Directory:  "/path/to/snapshots",
    Retention: 3,
    Step:      100,
    Gap:       1 * time.Millisecond,
    Progress: func(pages, remaining int) {
        fmt.Printf("copied %d of %d pages\n", pages-remaining, pages)
    },
})

func (*LiteDB) StartRead

func (ldb *LiteDB) StartRead(ctx scope.C) (*sql.Tx, CloseFunc, error)

StartRead creates a transaction useful for reading from the database.

No need to call sql.Tx.Commit; allow the rollback to close the transaction.

The sql.Tx must not be used for writing.

The CloseFunc must be called when done with the transaction.

func (*LiteDB) StartWrite

func (ldb *LiteDB) StartWrite(ctx scope.C) (*sql.Tx, CloseFunc, error)

StartWrite creates a transaction useful for writing to the database.

Must call sql.Tx.Commit to complete the transaction.

The sql.Tx may be used for reading and/or writing. Note that using a write transaction just for reading will be slower than a read only transaction.

The CloseFunc must be called when done with the transaction.

func (*LiteDB) Vacuum added in v1.3.1

func (ldb *LiteDB) Vacuum(ctx scope.C) error

Vacuum rebuilds the database file, reclaiming unused space and defragmenting data. It should be run sparingly (e.g., monthly or after many deletions) as it requires exclusive database access and can be slow for large databases. Improves performance by reducing file size and greatly improving sequential I/O efficiency.

type ProgressFunc added in v1.2.0

type ProgressFunc func(pages, remaining int)

ProgressFunc is a callback invoked between each snapshot step operation indicating the total number of pages, and the number of pages to be copied remaining.

type ScanFunc

type ScanFunc func(args ...any) error

ScanFunc represents the sql.Scan function from a sql.Rows object. This is used as the scan argument of the QueryRows package function, invoked on each element in the result set of the query to build the list of resulting items.

type SnapshotOptions added in v1.2.0

type SnapshotOptions struct {
	// The Directory in which to create the snapshot database file(s).
	//
	// Snapshot database filenames are in the form "snapshot-<timestamp>.db".
	// and the retention policy makes use of these timestamps to determine
	// which files to keep around or purge.
	Directory string

	// The Retention policy indicates the number of additional previous snapshot
	// database files to keep around after completion.
	//
	// Defaults to 0 (purge all but the latest snapshot).
	Retention int

	// Step indicates the number of pages to copy in each operation.
	//
	// A smaller step size (<128) will take longer to create the snapshot, but
	// will enable writers to make concurrent progress.
	//
	// A larger step size (>1024) will be fast, but may cause noticeable
	// pauses for other write operations.
	//
	// Use -1 to snapshot the entire database in a single operation. Default.
	Step int

	// Gap is amount of time to wait in between each step operation.
	//
	// A larger gap enables more write operations to happen concurrently, but
	// will cause more duplicate work, as each page that gets written to will
	// need to be copied again.
	//
	// Defaults to 1 millisecond.
	Gap time.Duration

	// Progress is an optional callback that is invoked after each step interval
	// for updating, providing information about the total number of pages and
	// the number of pages remaining to be copied.
	Progress ProgressFunc
}

SnapshotOptions defines options for creating a point-in-time snapshot of the database. Snapshots create a full copy of the database at a specific moment in time, which can be useful for backups, point-in-time recovery, or creating read-only copies of the database for sharing.

The snapshot is performed by copying the database page-by-page using SQLite's backup API. By default, the entire database is copied in a single operation, which is fast but may cause noticeable I/O pauses for other write operations. To enable concurrent writers to make progress, configure Step to copy a smaller number of pages at a time with a Gap between each step.

Snapshot database files are created in the specified Directory with timestamps in the filename (snapshot-<timestamp>.db). The Retention option controls how many previous snapshot files to keep around after each snapshot completes.

Directories

Path Synopsis
Package libsqltest enables convenient testing with a real sqlite3 database backed by memory for use in unit tests.
Package libsqltest enables convenient testing with a real sqlite3 database backed by memory for use in unit tests.

Jump to

Keyboard shortcuts

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