Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions sqlite3.go
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
writableSchema := -1
vfsName := ""
var cacheSize *int64
var pragmas []string
stmtCacheSize := 0

pos := strings.IndexRune(dsn, '?')
Expand Down Expand Up @@ -1295,6 +1296,9 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
}
}

// _pragma (generic, repeatable — executed in order on every connection)
pragmas = params["_pragma"]

// Busy Timeout (_busy_timeout)
//
// https://www.sqlite.org/pragma.html#pragma_busy_timeout
Expand Down Expand Up @@ -1880,6 +1884,14 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
}
}

// Generic _pragma parameters — run last so they override specific defaults
for _, p := range pragmas {
if err := exec(fmt.Sprintf("PRAGMA %s;", p)); err != nil {
C.sqlite3_close_v2(db)
return nil, err
}
}

if len(d.Extensions) > 0 {
if err := conn.loadExtensions(d.Extensions); err != nil {
conn.Close()
Expand Down
81 changes: 81 additions & 0 deletions sqlite3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2131,6 +2131,87 @@ func BenchmarkCustomFunctions(b *testing.B) {
}
}

func TestGenericPragma(t *testing.T) {
// Single _pragma applied correctly
t.Run("single", func(t *testing.T) {
tempFilename := TempFilename(t)
defer os.Remove(tempFilename)
db, err := sql.Open("sqlite3", tempFilename+"?_pragma=synchronous(NORMAL)")
if err != nil {
t.Fatal(err)
}
defer db.Close()

var mode int
if err := db.QueryRow("PRAGMA synchronous").Scan(&mode); err != nil {
t.Fatal(err)
}
if mode != 1 { // 1 = NORMAL
t.Errorf("expected synchronous=1 (NORMAL), got %d", mode)
}
})

// Multiple _pragma values applied in order
t.Run("multiple", func(t *testing.T) {
tempFilename := TempFilename(t)
defer os.Remove(tempFilename)
db, err := sql.Open("sqlite3", tempFilename+"?_pragma=synchronous(NORMAL)&_pragma=busy_timeout(1234)")
if err != nil {
t.Fatal(err)
}
defer db.Close()

var sync, timeout int
if err := db.QueryRow("PRAGMA synchronous").Scan(&sync); err != nil {
t.Fatal(err)
}
if err := db.QueryRow("PRAGMA busy_timeout").Scan(&timeout); err != nil {
t.Fatal(err)
}
if sync != 1 {
t.Errorf("expected synchronous=1 (NORMAL), got %d", sync)
}
if timeout != 1234 {
t.Errorf("expected busy_timeout=1234, got %d", timeout)
}
})

// PRAGMAs are re-applied on every connection the pool opens
t.Run("pool_reapply", func(t *testing.T) {
tempFilename := TempFilename(t)
defer os.Remove(tempFilename)
db, err := sql.Open("sqlite3", tempFilename+"?_pragma=synchronous(NORMAL)")
if err != nil {
t.Fatal(err)
}
defer db.Close()
// Force the pool to open multiple distinct connections
db.SetMaxOpenConns(4)

const workers = 4
errc := make(chan error, workers)
for i := 0; i < workers; i++ {
go func() {
var mode int
if err := db.QueryRow("PRAGMA synchronous").Scan(&mode); err != nil {
errc <- err
return
}
if mode != 1 {
errc <- fmt.Errorf("connection got synchronous=%d, want 1 (NORMAL)", mode)
return
}
errc <- nil
}()
}
for i := 0; i < workers; i++ {
if err := <-errc; err != nil {
t.Error(err)
}
}
})
}

func TestSuite(t *testing.T) {
initializeTestDB(t)
defer freeTestDB()
Expand Down