Documentation
¶
Overview ¶
Package sqlitedatabase provides a SQLite database.Driver on database/sql. Constructors configure a lazily opened pool and never run schema DDL. NewFrom takes ownership of the supplied pool, which the root Driver.Stop closes. New and NewFrom apply Builder policies directly once in order before validation. Nil entries panic; nil or empty slice expansions omit edits. Completed settings detach from retained drafts. NewFrom only applies positive pool limits, not New's DSN rewriting or in-memory single-connection default. Memory DSNs include :memory: with parameters, file::memory:, and file URIs whose mode parameter is memory. New defaults them to one connection unless a positive MaxOpenConns is supplied; private memory connections do not share contents. Closing the last connection destroys the memory database. Only native query parameters select _time_format; filename substrings do not.
driver, err := sqlitedatabase.New(":memory:", func(connection *sqlitedatabase.Builder) {
connection.MaxOpenConns(1)
})
if err != nil {
return err
}
db := database.New(driver)
WithSession retains one physical connection without starting a transaction:
err := driver.WithSession(ctx, func(session database.Driver) error {
return session.WithTransaction(ctx, func(tx database.Driver) error {
_, err := tx.Exec(ctx, "UPDATE counters SET value = value + 1")
return err
})
})
Transactions use the retained connection and nested transactions use savepoints. Named locks combine process-local exclusion with sibling file locks on supported Unix hosts. They are not reentrant. Transaction locks survive nested rollback; nontransactional session locks end with their owning callback unless explicitly unlocked earlier. Callback drivers and cursors expire on return, and cleanup closes outstanding results before releasing the connection. Native SQL returns the root pool and deliberately bypasses scopes. The driver owns native transaction completion instead of using sql.Tx's automatic rollback worker, so cancellation finishes rollback before managed locks release. Native BEGIN keeps its caller context. Native Commit/Rollback do not take contexts and are not bounded by the savepoint-cleanup timeout. Panic and runtime.Goexit both roll back rather than commit incomplete bodies.
Raw Driver.Select replaces previous destination elements; an empty result leaves an empty slice. A scanning failure can leave the successfully scanned prefix. The Database.Select facade creates a fresh typed result instead. Native close errors from Find and Select are preserved alongside scan errors and prevent a retained session from committing or being pooled as healthy.
Index ¶
- Variables
- type Builder
- type BuilderFunc
- type Driver
- func (driver *Driver) Dialect() query.Dialect
- func (driver *Driver) Exec(ctx context.Context, statement string, args ...any) (int64, error)
- func (driver *Driver) Find(ctx context.Context, statement string, dest any, args ...any) (err error)
- func (driver *Driver) Lock(ctx context.Context, name string) (func(context.Context) error, error)
- func (driver *Driver) Ping(ctx context.Context) error
- func (driver *Driver) Query(ctx context.Context, statement string, args ...any) (database.Rows, error)
- func (driver *Driver) SQL() *sql.DB
- func (driver *Driver) Select(ctx context.Context, statement string, dest any, args ...any) (err error)
- func (driver *Driver) Start(ctx context.Context) error
- func (driver *Driver) Stop(context.Context) error
- func (driver *Driver) TryLock(ctx context.Context, name string) (func(context.Context) error, bool, error)
- func (driver *Driver) WithSession(ctx context.Context, fn func(session database.Driver) error) (retErr error)
- func (driver *Driver) WithTransaction(ctx context.Context, fn func(tx database.Driver) error) error
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidConfig = errors.New("invalid SQLite configuration")
ErrInvalidConfig indicates that the arguments or pool settings given to New cannot establish a driver. Blank DSNs and negative pool limits fail before opening a database; native file/URI errors can still arise on first use.
Example:
_, err := sqlitedatabase.New(" ")
if errors.Is(err, sqlitedatabase.ErrInvalidConfig) {
return errors.New("check the database configuration")
}
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder edits optional pool limits during New or NewFrom. Zero leaves native limits unchanged, except New's single-connection in-memory default. Completed settings are copied; retained drafts cannot change a driver. Callbacks run once in order; supplied nil callbacks panic, nil slice expansion omits edits.
Example:
driver, err := sqlitedatabase.New("app.db", func(connection *sqlitedatabase.Builder) {
connection.MaxOpenConns(10)
})
if err != nil {
return err
}
return driver.Start(ctx)
func (*Builder) ConnMaxIdleTime ¶
ConnMaxIdleTime replaces the idle connection age and returns this draft. Only positive values apply to supplied pools; New rejects negative durations.
Example:
driver, err := sqlitedatabase.New("app.db", func(connection *sqlitedatabase.Builder) {
connection.ConnMaxIdleTime(time.Minute)
})
if err != nil {
return err
}
return driver.Start(ctx)
func (*Builder) ConnMaxLifetime ¶
ConnMaxLifetime replaces the connection age limit and returns this draft. Zero preserves the native limit; New rejects negative durations.
Example:
driver, err := sqlitedatabase.New("app.db", func(connection *sqlitedatabase.Builder) {
connection.ConnMaxLifetime(time.Hour)
})
if err != nil {
return err
}
return driver.Start(ctx)
func (*Builder) MaxIdleConns ¶
MaxIdleConns replaces the idle connection limit and returns this draft. Only positive values apply to supplied pools; New rejects negative limits.
Example:
driver, err := sqlitedatabase.New("app.db", func(connection *sqlitedatabase.Builder) {
connection.MaxIdleConns(2)
})
if err != nil {
return err
}
return driver.Start(ctx)
func (*Builder) MaxOpenConns ¶
MaxOpenConns replaces the open connection limit and returns this draft. Zero keeps native defaults; New rejects negative limits.
Example:
driver, err := sqlitedatabase.New("app.db", func(connection *sqlitedatabase.Builder) {
connection.MaxOpenConns(10)
})
if err != nil {
return err
}
return driver.Start(ctx)
type BuilderFunc ¶
type BuilderFunc = func(*Builder)
BuilderFunc edits a fresh construction draft supplied to New or NewFrom. Policies run synchronously once in order; nil entries panic, and retaining the draft does not let a caller change completed driver settings.
Example:
policy := sqlitedatabase.BuilderFunc(func(connection *sqlitedatabase.Builder) {
connection.MaxOpenConns(1)
})
driver, err := sqlitedatabase.New(":memory:", policy)
if err != nil {
return err
}
return driver.Start(ctx)
type Driver ¶
type Driver struct {
// contains filtered or unexported fields
}
Driver implements database.Driver for SQLite on database/sql, scanning rows with query.Targets. Callback drivers from WithSession and WithTransaction use one retained connection and expire when their callback returns. Native SQL access deliberately returns the root pool instead.
Example:
driver, err := sqlitedatabase.New(":memory:")
if err != nil {
return err
}
db := database.New(driver)
return db.WithSession(ctx, func(ctx context.Context) error {
_, err := db.Find[int](ctx, query.Raw("SELECT 1"))
return err
})
func New ¶
func New(dsn string, configure ...BuilderFunc) (*Driver, error)
New configures SQLite and returns a Driver. In-memory DSNs default to one connection, including ":memory:" with parameters, "file::memory:", and file URIs with mode=memory. An explicit positive MaxOpenConns overrides that default; private memory databases then have independent contents per connection. Closing the last connection destroys a memory database, including after pool expiry.
dsn is the SQLite data source name. It is required and has no default; a blank dsn returns an error wrapping ErrInvalidConfig naming the data source name, as does a negative pool limit. Policies edit Builder directly once in order before validation; nil entries panic. No database opens until Driver.Start or the first operation. Completed settings detach from retained drafts. Use NewFrom to wrap a pool the caller already opened.
New adds _time_format=sqlite unless the native query parameters already set _time_format. Filename text does not select this policy. The default emits SQLite timestamp text rather than Go's time.Time.String text. Values written to columns declared DATE, DATETIME, or TIMESTAMP (the types the query builder uses for Date, Timestamp, and TimestampTZ on query.SQLite) round-trip exactly, offset included, and CURRENT_TIMESTAMP defaults scan too. SQLite has no timestamp type and compares these values as text, so ordering and comparisons are correct only when every value is written in UTC; convert with time.Time.UTC before writing.
Example:
driver, err := sqlitedatabase.New(dsn, func(connection *sqlitedatabase.Builder) {
connection.MaxOpenConns(10)
})
if err != nil {
return err
}
return driver.Start(ctx)
func NewFrom ¶
func NewFrom(pool *sql.DB, configure ...BuilderFunc) *Driver
NewFrom wraps a SQLite pool the caller opened, for example with database/sql.Open and the "sqlite" driver. The driver takes ownership: Driver.Stop closes the pool.
Only positive Builder pool limits apply, on top of the limits already set on pool. New's in-memory single-connection rule and its time format are not applied to a pool opened elsewhere. Native connections must implement database/sql/driver.ConnBeginTx, as the pinned modernc driver does, so BEGIN uses its caller context without an independent sql.Tx rollback worker. Other native profiles fail transactions with an error matching errors.ErrUnsupported rather than dropping cancellation. Policies run once in order on one draft; nil entries panic and expanded nil or empty slices omit edits. Retained drafts cannot change the pool later.
Example:
driver := sqlitedatabase.NewFrom(pool, func(connection *sqlitedatabase.Builder) {
connection.MaxOpenConns(10)
})
return driver.Stop(ctx)
func (*Driver) Dialect ¶
Dialect reports query.SQLite for roots and callback handles alike. Use it when compiling SQL placeholders and identifiers for a native operation.
Example:
statement, err := query.Raw("SELECT ?", 7).Compile(driver.Dialect())
if err != nil {
return err
}
_, err = driver.Exec(ctx, statement.SQL(), statement.Args()...)
func (*Driver) Exec ¶
Exec executes a statement that modifies data and returns the number of rows it affected. Callback drivers use only their retained executor; an ended callback or canceled enclosing scope fails before native SQL is sent.
Example:
changed, err := driver.Exec(ctx, "UPDATE counters SET value = value + 1 WHERE id = ?", id)
func (*Driver) Find ¶
func (driver *Driver) Find(ctx context.Context, statement string, dest any, args ...any) (err error)
Find scans the first result row into dest, a pointer to a struct or to a single-column value. When no row matches, the returned error wraps both sql.ErrNoRows and database.ErrNoRows. Cursor-close failures are returned alongside scan or iteration errors, and make a retained session unsafe to reuse.
Example:
var value int
if err := driver.Find(ctx, "SELECT value FROM counters WHERE id = ?", &value, id); err != nil {
return err
}
func (*Driver) Lock ¶
Lock blocks until the named lock is held or ctx ends. The returned unlock function releases the lock and is safe to call more than once.
The lock is an in-process lock shared by all drivers in the process and, for a file-backed database on Linux, macOS, or a BSD, an operating system file lock on a sibling lock file, so the lock also spans processes. Inside a transaction the lock is held until the transaction ends and unlock does nothing. In an ordinary session callback, remaining locks are released on callback return. An inner session owns its new locks without adopting its parent's.
Example:
unlock, err := driver.Lock(ctx, "nightly-report")
if err != nil {
return err
}
defer unlock(context.WithoutCancel(ctx))
func (*Driver) Ping ¶
Ping verifies connectivity through the pool on the root and the current retained executor in a callback. It never allocates a second scoped connection. An expired callback fails with its session or transaction lifetime sentinel.
Example:
if err := driver.Ping(ctx); err != nil {
return err
}
func (*Driver) Query ¶
func (driver *Driver) Query(ctx context.Context, statement string, args ...any) (database.Rows, error)
Query executes a statement and returns a cursor over its rows. Close it when done because it retains connection capacity. Callback cursors are also closed automatically on return and reject later iteration with the scope's sentinel.
Example:
rows, err := driver.Query(ctx, "SELECT value FROM counters ORDER BY id")
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var value int
if err := rows.Scan(&value); err != nil {
return err
}
}
return rows.Err()
func (*Driver) SQL ¶
SQL returns the underlying database/sql pool, accepted by any Go SQL library. It is always the pool, also when called on the driver of a transaction, so queries made through it run outside any transaction. Native calls also bypass callback lifetime guards and facade telemetry; keep protected work on the callback driver or its facade context instead.
Example:
stats := driver.SQL().Stats() openConnections := stats.OpenConnections
func (*Driver) Select ¶
func (driver *Driver) Select(ctx context.Context, statement string, dest any, args ...any) (err error)
Select scans every result row into dest, a pointer to a slice of structs, of pointers to structs, or of single-column values. Struct columns follow query.Targets. A successful query replaces existing elements; no rows leaves dest empty. A scan failure can leave the successfully scanned prefix. Invalid destinations and expired callbacks fail before any SQL is sent. Cursor-close failures are joined with scan/iteration errors and poison a retained session.
Example:
ids := []int{99}
err := driver.Select(ctx, "SELECT id FROM reports ORDER BY id", &ids)
func (*Driver) Start ¶
Start verifies connectivity so a misconfigured database fails fast. The pool itself is opened lazily by New. On callback drivers, readiness runs through the retained executor and rejects an expired callback.
Example:
if err := driver.Start(ctx); err != nil {
return err
}
func (*Driver) Stop ¶
Stop closes the owned root connection pool through database/sql, including a pool transferred through NewFrom. It ignores ctx, so an expired context does not skip release. Finish callbacks and close cursors before stopping. On a callback driver Stop is a no-op because only the root owns the pool.
Example:
if err := driver.Stop(context.Background()); err != nil {
return err
}
func (*Driver) TryLock ¶
func (driver *Driver) TryLock(ctx context.Context, name string) (func(context.Context) error, bool, error)
TryLock takes the named lock without waiting. It reports acquired false, with a nil error, when another holder has the lock. The returned unlock function releases the lock and is safe to call more than once.
The lock is the one Driver.Lock takes, so the two exclude each other in the process and, where Driver.Lock spans processes, across them. Inside a transaction the lock is held until the transaction ends and unlock does nothing.
Example:
unlock, acquired, err := driver.TryLock(ctx, "nightly-report")
if err != nil || !acquired {
return err
}
defer unlock(ctx)
func (*Driver) WithSession ¶
func (driver *Driver) WithSession(ctx context.Context, fn func(session database.Driver) error) (retErr error)
WithSession retains one physical SQLite connection without starting a transaction. Nested calls borrow it but expire their own callback drivers and cursors on return. Errors do not undo committed statements, and failed cleanup discards the connection rather than allowing unrelated work to reuse it.
Example:
err := driver.WithSession(ctx, func(session database.Driver) error {
_, err := session.Exec(ctx, "CREATE TEMPORARY TABLE selected (id INTEGER)")
return err
})
func (*Driver) WithTransaction ¶
WithTransaction begins on a retained physical connection, or uses a savepoint when an enclosing transaction exists. Nested locks belong to the outermost transaction and survive savepoint rollback. Callback drivers and cursors expire on return, even while an enclosing transaction remains live.
A body error rolls back. Panic cleanup re-raises the original panic. Cleanup errors after a confirmed commit wrap database.ErrAfterCommit, so callers must not retry committed work. Session-owned locks predating this transaction are not released by its cleanup. The driver owns native transaction completion: caller cancellation requests synchronous rollback before managed locks are released, rather than relying on database/sql's asynchronous sql.Tx cancellation worker. Native BEGIN still uses the caller context. A callback that exits via runtime.Goexit rolls back. Native Commit and Rollback have no context parameter and can outlive the savepoint cleanup budget; this driver does not claim to interrupt those calls.
Example:
err := driver.WithTransaction(ctx, func(tx database.Driver) error {
_, err := tx.Exec(ctx, "UPDATE counters SET value = value + 1")
return err
})