mirror of
https://github.com/hanzoai/sqlite.git
synced 2026-10-30 18:22:26 +00:00
Two backend-neutral primitives so hanzoai/replicate can route through hanzoai/sqlite and stop blank-importing modernc.org/sqlite — which under CGO=1 registers 'sqlite' a SECOND time (mattn already registered it), the live 'sql: Register called twice for driver sqlite' panic in base/store/replicator (base -> commerce -> cloud all embed replicate, so this would panic the cgo cloud binary at startup). - SetPersistWAL(rawConn, on): SQLITE_FCNTL_PERSIST_WAL via mattn SetFileControlInt (cgo) / modernc FileControl.FileControlPersistWAL (nocgo). Keeps -wal across close (WAL-shipping needs it). Proven: -wal survives db.Close() on both backends. - OpenPragma(dsn, pragmas): a database/sql Connector applying pragmas to EVERY pooled connection at connect. wal_autocheckpoint is the reason: mattn drops it from the DSN, so a naive port would re-enable auto-checkpoint and truncate the WAL under a replicator (data loss). Per-connection ExecContext is backend-neutral. Proven on 3 concurrent conns under both backends (busy_timeout/journal_mode/wal_autocheckpoint/ foreign_keys all applied per-conn). Tag-neutral driver resolution. All three tag combos green (modernc, mattn, mattn+sqlite_purego); vet clean. LLM.md -> v0.2.3.
115 lines
3.6 KiB
Go
115 lines
3.6 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// TestOpenPragmaAppliesPerConnection is the load-bearing guarantee: pragmas
|
|
// passed to OpenPragma take effect on EVERY pooled connection under whichever
|
|
// backend this build links — including wal_autocheckpoint, which mattn drops
|
|
// from the DSN. Proven by forcing three distinct live connections and reading
|
|
// each one's pragma state.
|
|
func TestOpenPragmaAppliesPerConnection(t *testing.T) {
|
|
ctx := context.Background()
|
|
path := filepath.Join(t.TempDir(), "pragma.db")
|
|
|
|
db, err := OpenPragma("file:"+path, []Pragma{
|
|
{Name: "busy_timeout", Value: "5000"},
|
|
{Name: "journal_mode", Value: "WAL"},
|
|
{Name: "wal_autocheckpoint", Value: "0"},
|
|
{Name: "foreign_keys", Value: "1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("OpenPragma: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Pin three connections open simultaneously so the pool must mint three
|
|
// distinct backend conns; each must carry the pragmas.
|
|
db.SetMaxOpenConns(3)
|
|
conns := make([]*sql.Conn, 3)
|
|
for i := range conns {
|
|
c, err := db.Conn(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Conn %d: %v", i, err)
|
|
}
|
|
conns[i] = c
|
|
defer c.Close()
|
|
}
|
|
|
|
for i, c := range conns {
|
|
var busyTimeout, walAutockpt, foreignKeys int
|
|
var journalMode string
|
|
if err := c.QueryRowContext(ctx, "PRAGMA busy_timeout").Scan(&busyTimeout); err != nil {
|
|
t.Fatalf("conn %d busy_timeout: %v", i, err)
|
|
}
|
|
if err := c.QueryRowContext(ctx, "PRAGMA journal_mode").Scan(&journalMode); err != nil {
|
|
t.Fatalf("conn %d journal_mode: %v", i, err)
|
|
}
|
|
if err := c.QueryRowContext(ctx, "PRAGMA wal_autocheckpoint").Scan(&walAutockpt); err != nil {
|
|
t.Fatalf("conn %d wal_autocheckpoint: %v", i, err)
|
|
}
|
|
if err := c.QueryRowContext(ctx, "PRAGMA foreign_keys").Scan(&foreignKeys); err != nil {
|
|
t.Fatalf("conn %d foreign_keys: %v", i, err)
|
|
}
|
|
if busyTimeout != 5000 {
|
|
t.Errorf("conn %d busy_timeout = %d, want 5000", i, busyTimeout)
|
|
}
|
|
if journalMode != "wal" {
|
|
t.Errorf("conn %d journal_mode = %q, want wal", i, journalMode)
|
|
}
|
|
if walAutockpt != 0 {
|
|
t.Errorf("conn %d wal_autocheckpoint = %d, want 0 (mattn drops this from the DSN — the whole point)", i, walAutockpt)
|
|
}
|
|
if foreignKeys != 1 {
|
|
t.Errorf("conn %d foreign_keys = %d, want 1", i, foreignKeys)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSetPersistWAL proves the file control keeps the -wal file across close on
|
|
// whichever backend this build links (SQLite otherwise deletes -wal on the last
|
|
// clean close), and that a wrong conn type errors instead of panicking.
|
|
func TestSetPersistWAL(t *testing.T) {
|
|
ctx := context.Background()
|
|
path := filepath.Join(t.TempDir(), "persist.db")
|
|
|
|
db, err := OpenDB(path, nil) // OpenDB's DSN sets journal_mode=WAL
|
|
if err != nil {
|
|
t.Fatalf("OpenDB: %v", err)
|
|
}
|
|
|
|
conn, err := db.Conn(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Conn: %v", err)
|
|
}
|
|
if err := conn.Raw(func(dc any) error { return SetPersistWAL(dc, true) }); err != nil {
|
|
t.Fatalf("SetPersistWAL: %v", err)
|
|
}
|
|
// Write on the SAME connection so the -wal file is created under persist_wal.
|
|
if _, err := conn.ExecContext(ctx, `CREATE TABLE t (x INTEGER)`); err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
if _, err := conn.ExecContext(ctx, `INSERT INTO t VALUES (1)`); err != nil {
|
|
t.Fatalf("insert: %v", err)
|
|
}
|
|
conn.Close()
|
|
if err := db.Close(); err != nil {
|
|
t.Fatalf("close: %v", err)
|
|
}
|
|
|
|
if _, err := os.Stat(path + "-wal"); err != nil {
|
|
t.Fatalf("-wal file was not kept across close (PERSIST_WAL not honored): %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSetPersistWALWrongConn(t *testing.T) {
|
|
if err := SetPersistWAL(struct{}{}, true); err == nil {
|
|
t.Fatal("expected an error for a non-sqlite driver conn")
|
|
}
|
|
}
|