Files
mpc/pkg/infra/sqlite_driver_test.go
zeekayandHanzo Dev 7be332b97d fix(infra): route SQLiteMeta through hanzoai/sqlite — one "sqlite" registrar
pkg/infra blank-imported modernc.org/sqlite, so any binary linking this store
next to another Hanzo store (base/core, orm/db, tasks' shard store — all of
which reach SQLite through github.com/hanzoai/sqlite) registered the "sqlite"
database/sql driver name twice and panicked during init:

    panic: sql: Register called twice for driver sqlite
    modernc.org/sqlite.init.0()  modernc.org/[email protected].0/sqlite.go:57

hanzoai/mpc's mpcd is exactly that binary; `go test ./...` there could not even
start. hanzoai/sqlite is the ecosystem's single registrar of that name (pure-Go
modernc under !cgo, hanzoai/csqlite+SQLCipher under cgo), so this store must go
through it — and so must base and orm, hence base v0.39.1 -> v1.5.8 and orm
v0.5.2 -> v0.6.16, the versions that route through it. mpcd's package graph now
has exactly one importer of a SQLite driver.

The hand-rolled DSN went with it. `?_journal_mode=WAL&_busy_timeout=5000&...`
is mattn syntax and the pure-Go backend this module actually linked dropped it
silently — measured journal_mode=delete, busy_timeout=0, foreign_keys=0, i.e.
no WAL, immediate SQLITE_BUSY under concurrent writers and no FK enforcement.
sqlite.PragmaDSN encodes the pragma set in the ACTIVE backend's syntax, so all
three apply under both backends.

Tests: sqlite_driver_test.go links base/core and orm/db alongside this store, so
a regression to a direct modernc import blows the test binary up in init;
TestSQLiteMeta_PragmasApplied reads the pragmas back off a file-backed WAL
store. Both pass under CGO_ENABLED=0 and CGO_ENABLED=1.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 10:52:57 -07:00

65 lines
2.3 KiB
Go

package infra
import (
"database/sql"
"path/filepath"
"slices"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
// The other Hanzo stores a single binary links alongside SQLiteMeta — mpcd
// links all three. Each must reach SQLite through github.com/hanzoai/sqlite,
// the ONE package that registers the "sqlite" driver name. If any of them
// (including this one) regresses to importing modernc.org/sqlite directly,
// the second sql.Register panics while this test binary is still running
// init and the whole package fails to start. That init-time explosion is
// the teeth of this test; the assertions below are the readable part.
_ "github.com/hanzoai/base/core"
_ "github.com/hanzoai/orm/db"
)
// TestSQLiteDriverRegisteredOnce pins the invariant that broke hanzoai/mpc:
// linking SQLiteMeta next to base/core and orm/db must yield exactly one
// registration of the "sqlite" driver name.
func TestSQLiteDriverRegisteredOnce(t *testing.T) {
drivers := sql.Drivers()
assert.True(t, slices.Contains(drivers, "sqlite"),
"no %q driver registered; hanzoai/sqlite must register it: %v", "sqlite", drivers)
// sql.Register panics on a duplicate name, so reaching this line already
// proves single registration. Assert it anyway so the intent is legible.
var n int
for _, d := range drivers {
if d == "sqlite" {
n++
}
}
assert.Equal(t, 1, n, "driver %q registered %d times", "sqlite", n)
}
// TestSQLiteMeta_PragmasApplied proves the connection DSN is expressed in the
// ACTIVE backend's syntax. The previous hand-rolled `?_journal_mode=WAL&...`
// string was mattn-only: the pure-Go backend silently dropped it, leaving
// journal_mode=delete and busy_timeout=0 — immediate SQLITE_BUSY under
// concurrent writers. sqlite.PragmaDSN encodes them per backend.
func TestSQLiteMeta_PragmasApplied(t *testing.T) {
s, err := NewSQLiteMeta(SQLiteMetaConfig{
Path: filepath.Join(t.TempDir(), "meta.db"),
WAL: true,
})
require.NoError(t, err)
t.Cleanup(func() { s.Close() })
pragma := func(name string) string {
var v string
require.NoError(t, s.db.NewQuery("PRAGMA "+name).Row(&v), "read pragma %s", name)
return v
}
assert.Equal(t, "wal", pragma("journal_mode"))
assert.Equal(t, "5000", pragma("busy_timeout"))
assert.Equal(t, "1", pragma("foreign_keys"))
}