• v0.9.0 237cba9bad

    Rene Nochebuena released this 2026-03-19 07:26:31 -06:00 | 0 commits to main since this release

    v0.9.0

    code.nochebuena.dev/go/sqlite

    Overview

    sqlite is a pure-Go, CGO-free SQLite client backed by modernc.org/sqlite. It integrates
    with the launcher lifecycle and health check systems, serialises write transactions through
    a mutex to prevent SQLITE_BUSY errors under concurrent goroutines, and provides a
    UnitOfWork implementation that injects the active transaction into the context.

    This is the first stable release. The API was designed through multiple architecture reviews
    and validated end-to-end via the todo-api proof-of-concept. It is versioned at v0.9.0 rather
    than v1.0.0 because the library has not yet been exercised in production across all edge cases;
    the pre-1.0 version preserves the option for minor API refinements without a major bump.

    What's Included

    • Executor interface: ExecContext, QueryContext, QueryRowContext using database/sql
      native types (sql.Result, *sql.Rows, *sql.Row)
    • Tx interface: extends Executor with Commit() / Rollback() — no context arguments,
      matching the honest database/sql contract
    • Client interface: GetExecutor(ctx), Begin(ctx), Ping(ctx), HandleError(err)
    • Component interface: composes launcher.Component, health.Checkable, and Client
    • New(logger, cfg) Component constructor; database opened in OnInit, not at construction
    • Config struct with env-tag support (SQLITE_PATH, SQLITE_MAX_OPEN_CONNS,
      SQLITE_MAX_IDLE_CONNS, SQLITE_PRAGMAS)
    • Default Pragmas: WAL journal mode, 5 000 ms busy timeout, foreign key enforcement
      (?_journal=WAL&_timeout=5000&_fk=true)
    • PRAGMA foreign_keys = ON enforced explicitly in OnInit; startup fails if the PRAGMA
      cannot be set
    • writeMu sync.Mutex in sqliteComponent; acquired by UnitOfWork.Do before every write
      transaction to prevent SQLITE_BUSY
    • UnitOfWork interface and NewUnitOfWork(logger, client) constructor using context injection
    • HandleError(err) error mapping SQLite extended error codes to xerrors:
      • Code 2067 (SQLITE_CONSTRAINT_UNIQUE) and 1555 (SQLITE_CONSTRAINT_PRIMARYKEY) → ErrAlreadyExists
      • Code 787 (SQLITE_CONSTRAINT_FOREIGNKEY) → ErrInvalidInput
      • sql.ErrNoRowsErrNotFound
      • all other errors → ErrInternal
    • health.Checkable implementation at health.LevelCritical
    • Pure-Go driver: no CGO, no system libraries; cross-compilation works with CGO_ENABLED=0

    Installation

    go get code.nochebuena.dev/go/sqlite@v0.9.0
    

    Requires Go 1.21 or later. Depends on code.nochebuena.dev/go/health,
    code.nochebuena.dev/go/launcher, code.nochebuena.dev/go/logz,
    code.nochebuena.dev/go/xerrors, and modernc.org/sqlite.

    No CGO toolchain is required. Build with CGO_ENABLED=0 without restriction.

    Design Highlights

    Pure-Go driver. modernc.org/sqlite is used instead of mattn/go-sqlite3. No CGO, no
    system SQLite library required. Binaries are fully self-contained and cross-compile without
    a C toolchain.

    Write serialisation via mutex. writeMu sync.Mutex is acquired by UnitOfWork.Do before
    every write transaction. SQLite's single-writer constraint means concurrent write attempts
    would otherwise receive SQLITE_BUSY. The mutex eliminates that race at the application layer.

    Foreign key enforcement. Enabled in both the default DSN Pragmas (_fk=true) and via an
    explicit PRAGMA foreign_keys = ON in OnInit. SQLite disables foreign keys by default;
    both layers ensure enforcement even if Pragmas are overridden.

    Honest Tx contract. Tx.Commit() and Tx.Rollback() take no context, matching the
    database/sql limitation. The interface documents this explicitly.

    UnitOfWork via context injection. GetExecutor(ctx) checks for a ctxTxKey{} in context.
    Inside UnitOfWork.Do, the context carries the active transaction; repositories participate
    automatically.

    Known Limitations & Edge Cases

    • Single-writer serialisation limits write throughput. The writeMu mutex means all
      write transactions are sequential. This is appropriate for embedded, single-process use
      cases; it is not suitable for high-concurrency write workloads.
    • No migration helper. Schema migrations are out of scope.
    • :memory: databases are connection-scoped. If MaxOpenConns > 1, each connection
      sees its own in-memory database. For in-memory use, keep MaxOpenConns = 1.
    • No context on Tx.Commit() / Tx.Rollback(). A commit or rollback cannot be
      cancelled by a deadline or context cancellation.
    • Pragmas override replaces defaults entirely. Callers who set Config.Pragmas must
      include all required pragmas; only the FK PRAGMA in OnInit is guaranteed regardless of
      the Pragmas field.

    v0.9.0 → v1.0.0 Roadmap

    • Evaluate whether read-only queries can bypass the write mutex to allow concurrent reads
      while a write transaction is in progress.
    • Consider a dedicated read connection and a write connection to implement WAL reader/writer
      separation at the pool level.
    • Assess whether Pragmas should be a structured type instead of a raw DSN query string.
    • Gather production feedback on the write mutex approach across real workloads.
    Downloads