The package-doc examples showed a fictional launcher.Register / health.Register API (and launcher.Append as a package func). Corrected to the real wiring: lc.Append + web/health.NewHandler(...).ServeHTTP. Documentation-only. No code, API, or dependency changes; version stays v1.1.0 (the v1.1.0 tag is moved to include this fix). Verified by compiling the corrected pattern against the real API.
131 lines
4.0 KiB
Markdown
131 lines
4.0 KiB
Markdown
# einherjar/db-sqlite
|
|
|
|
[](https://code.nochebuena.dev/einherjar/db-sqlite)
|
|
[](LICENSE)
|
|
[](https://go.dev)
|
|
[]()
|
|
|
|
> Not every hall needs pillars that reach the sky. Sometimes what matters fits in a single room, carried wherever the warrior goes.
|
|
|
|
`code.nochebuena.dev/einherjar/db-sqlite` is the SQLite database component of the Einherjar framework. It uses the pure-Go `modernc.org/sqlite` driver (no CGO, cross-compilation works without a C toolchain), wraps `database/sql` behind a lifecycle-aware `Component`, and serializes writes through a mutex to prevent `SQLITE_BUSY` under concurrent goroutines. WAL mode is enabled by default.
|
|
|
|
---
|
|
|
|
## Usage
|
|
|
|
### Setup
|
|
|
|
```go
|
|
import dbsqlite "code.nochebuena.dev/einherjar/db-sqlite"
|
|
|
|
db := dbsqlite.New(logger, dbsqlite.DefaultConfig())
|
|
lc.Append(db) // OnInit opens database; OnStop closes it
|
|
// db is observability.Checkable (LevelDegraded):
|
|
srv.Get("/health", health.NewHandler(logger, db).ServeHTTP)
|
|
```
|
|
|
|
### Querying
|
|
|
|
```go
|
|
// GetExecutor returns the pool when called outside a UnitOfWork.
|
|
rows, err := db.GetExecutor(ctx).QueryContext(ctx, "SELECT id, name FROM users WHERE active = ?", true)
|
|
defer rows.Close()
|
|
|
|
var name string
|
|
err := db.GetExecutor(ctx).QueryRowContext(ctx, "SELECT name FROM users WHERE id = ?", id).Scan(&name)
|
|
```
|
|
|
|
### Manual transaction
|
|
|
|
```go
|
|
tx, err := db.Begin(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return tx.Commit()
|
|
```
|
|
|
|
Note: `Commit` and `Rollback` do not accept a context — this is a `database/sql` limitation.
|
|
|
|
### Unit of work (recommended)
|
|
|
|
`UnitOfWork` wraps the transaction in context so every call to `GetExecutor` inside `uow.Do` automatically returns the active transaction. The internal write mutex prevents concurrent `SQLITE_BUSY` errors; only one goroutine can write at a time.
|
|
|
|
```go
|
|
uow := dbsqlite.NewUnitOfWork(logger, db)
|
|
|
|
err := uow.Do(ctx, func(ctx context.Context) error {
|
|
_, err := db.GetExecutor(ctx).ExecContext(ctx, "INSERT INTO orders (...) VALUES (...)", ...)
|
|
return err
|
|
})
|
|
```
|
|
|
|
If the function returns an error, the transaction is rolled back. If it returns nil, the transaction is committed.
|
|
|
|
### Error handling
|
|
|
|
```go
|
|
if err := db.HandleError(someErr); err != nil {
|
|
// SQLite error codes mapped to xerrors:
|
|
// UNIQUE constraint failed → ErrAlreadyExists
|
|
// FOREIGN KEY constraint → ErrPreconditionFailed
|
|
// NOT NULL constraint → ErrInvalidInput
|
|
// context.Canceled → ErrCancelled
|
|
// context.DeadlineExceeded → ErrDeadlineExceeded
|
|
}
|
|
```
|
|
|
|
`HandleError` is also available as a package-level function: `dbsqlite.HandleError(err)`.
|
|
|
|
---
|
|
|
|
## Environment variables
|
|
|
|
| Variable | Required | Default | Description |
|
|
|---|---|---|---|
|
|
| `EINHERJAR_SQLITE_PATH` | Yes | — | Path to the SQLite database file |
|
|
| `EINHERJAR_SQLITE_MAX_OPEN_CONNS` | No | `1` | Maximum open connections (keep at 1 for writes) |
|
|
| `EINHERJAR_SQLITE_MAX_IDLE_CONNS` | No | `1` | Maximum idle connections |
|
|
| `EINHERJAR_SQLITE_PRAGMAS` | No | `?_journal=WAL&_timeout=5000&_fk=true` | DSN pragma string |
|
|
|
|
The default pragma string enables WAL mode (better concurrent reads), a 5-second busy timeout, and foreign key enforcement.
|
|
|
|
---
|
|
|
|
## Dependency graph
|
|
|
|
```
|
|
contracts (zero dependencies)
|
|
↑
|
|
core
|
|
↑
|
|
db-sqlite (contracts, core, modernc.org/sqlite)
|
|
↑
|
|
your app
|
|
```
|
|
|
|
No CGO. Cross-compiles to any GOOS/GOARCH without a C toolchain.
|
|
|
|
---
|
|
|
|
## Verification
|
|
|
|
```bash
|
|
cd db-sqlite/
|
|
go build ./...
|
|
go vet ./...
|
|
go test ./...
|
|
gofmt -l .
|
|
```
|
|
|
|
---
|
|
|
|
> *The hall is built before the warriors arrive.*
|
|
> *That is the only guarantee worth making.*
|