purego-sqlite
Go bindings for SQLite via purego instead of cgo.
Linux only. CGO_ENABLED=0.
[!WARNING]
This is early stage. The full SQLite C API (~350 functions) is bound, but the database/sql driver covers the common path only. API may change.
Architecture
The binding layer is generated from sqlite3.h. The generator parses the system header and produces:
- purego function bindings for all ~350 non-variadic SQLite functions
- Typed SyscallN wrappers for variadic functions at known arities
- Outbound port interfaces grouped by API surface (Lifecycle, Prepare, Column, Bind, etc.)
- A composite CAPI interface embedding all port groups
- Inbound port interfaces (DB, Stmt, Rows, Result) for the public API
- Public API composition root wiring everything together
A hand-written core domain (~500 LOC) implements the business logic: open, prepare, bind, step, scan, close. A thin database/sql/driver adapter sits on top.
Mocks for all interfaces are generated by Mockery v3 and shipped in sqlite/mocks/.
Usage
database/sql driver
import (
"database/sql"
_ "github.com/bnema/purego-sqlite/driver"
)
db, err := sql.Open("sqlite3", "./my.db")
Direct API
import "github.com/bnema/purego-sqlite/sqlite"
db, err := sqlite.Open("./my.db")
defer db.Close()
stmt, _ := db.Prepare("SELECT name FROM users WHERE id = ?")
rows, _ := stmt.Query(42)
Testing with mocks
The package ships pre-generated Mockery v3 mocks for all public interfaces. Your code accepts sqlite.DB (an interface), and in tests you swap in a mock — no libsqlite3.so needed.
Step 1: Accept the interface in your code
package userrepo
import "github.com/bnema/purego-sqlite/sqlite"
type UserRepo struct {
db sqlite.DB
}
func New(db sqlite.DB) *UserRepo {
return &UserRepo{db: db}
}
func (r *UserRepo) GetName(id int) (string, error) {
rows, err := r.db.Query("SELECT name FROM users WHERE id = ?", id)
if err != nil {
return "", err
}
defer rows.Close()
if !rows.Next() {
return "", fmt.Errorf("user %d not found", id)
}
var name string
if err := rows.Scan(&name); err != nil {
return "", err
}
return name, nil
}
Step 2: Mock it in tests
package userrepo_test
import (
"testing"
"github.com/bnema/purego-sqlite/sqlite/mocks"
"github.com/stretchr/testify/require"
)
func TestGetName(t *testing.T) {
// Create mocks — expectations are auto-asserted on cleanup
mockDB := mocks.NewMockDB(t)
mockRows := mocks.NewMockRows(t)
// Set up the call chain
mockDB.EXPECT().
Query("SELECT name FROM users WHERE id = ?", 42).
Return(mockRows, nil)
mockRows.EXPECT().Next().Return(true)
mockRows.EXPECT().Scan().RunAndReturn(func(dest ...any) error {
*(dest[0].(*string)) = "Alice"
return nil
})
mockRows.EXPECT().Close().Return(nil)
// Test
repo := userrepo.New(mockDB)
name, err := repo.GetName(42)
require.NoError(t, err)
require.Equal(t, "Alice", name)
}
Available mocks in github.com/bnema/purego-sqlite/sqlite/mocks:
| Mock |
Interface |
Key methods |
MockDB |
sqlite.DB |
Prepare, Exec, Query, Close |
MockStmt |
sqlite.Stmt |
Exec, Query, NumInput, Close |
MockRows |
sqlite.Rows |
Next, Scan, Columns, Err, Close |
MockResult |
sqlite.Result |
LastInsertId, RowsAffected |
All mocks support .EXPECT() for type-safe expectations and .On()/.Return() for classic testify style.
Note: The driver registers as sqlite3, the same name as mattn/go-sqlite3.
Do not import both in the same binary.
Requirements
- Go 1.23+
libsqlite3.so on the system (present on virtually all Linux distros)
- Override path:
SQLITE_LIB_PATH=/custom/path/libsqlite3.so
Building
CGO_ENABLED=0 go build ./...
CGO_ENABLED=0 go test ./...
Integration tests need libsqlite3.so:
go test -tags integration ./integration/
Regenerating bindings
go generate ./...
Or manually:
go run ./cmd/sqlitegen --header /usr/include/sqlite3.h --output-dir .
mockery
Project layout
sqlite/ public API (generated) + mocks
driver/ database/sql adapter (hand-written)
internal/
ports/in/ inbound port interfaces (generated)
ports/out/ outbound port interfaces (generated) + mocks
core/ domain logic: open, prepare, bind, step, scan (hand-written)
capi/ purego bindings + bridge (generated + hand-written)
loader/ dlopen libsqlite3.so (hand-written)
cmd/sqlitegen/ binding generator (parser, model, emitter, templates)
integration/ integration tests (require libsqlite3.so)
License
MIT