diff --git a/sqlutil/db.go b/sqlutil/db.go index 531683440c453fa43dca7c0c129f2173f34addbc..3b8bd3db37bd134126bd929ac772da9f5782f90f 100644 --- a/sqlutil/db.go +++ b/sqlutil/db.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "log" + "net/url" "strings" _ "github.com/mattn/go-sqlite3" @@ -13,11 +14,24 @@ import ( // DebugMigrations can be set to true to dump statements to stderr. var DebugMigrations bool -const defaultOptions = "?cache=shared&_busy_timeout=10000&_journal=WAL&_sync=OFF" +// See https://github.com/mattn/go-sqlite3/issues/209 for details on +// why these default parameters were chosen. WAL mode is mandatory for +// external litestream support. +func defaultOptions() url.Values { + v := make(url.Values) + v.Set("cache", "shared") + v.Set("_journal", "WAL") + v.Set("_sync", "OFF") + v.Set("_busy_timeout", "999999") + v.Set("_fk", "true") + v.Set("_cache_size", "268435456") + v.Set("_auto_vacuum", "incremental") + return v +} type sqlOptions struct { migrations []func(*sql.Tx) error - sqlopts string + sqlopts url.Values } type Option func(*sqlOptions) @@ -28,16 +42,16 @@ func WithMigrations(migrations []func(*sql.Tx) error) Option { } } -func WithSqliteOptions(sqlopts string) Option { +func WithSqliteOption(opt, value string) Option { return func(opts *sqlOptions) { - opts.sqlopts = sqlopts + opts.sqlopts.Set(opt, value) } } // OpenDB opens a SQLite database and runs the database migrations. func OpenDB(dburi string, options ...Option) (*sql.DB, error) { var opts sqlOptions - opts.sqlopts = defaultOptions + opts.sqlopts = defaultOptions() for _, o := range options { o(&opts) } @@ -45,7 +59,7 @@ func OpenDB(dburi string, options ...Option) (*sql.DB, error) { // Add sqlite3-specific parameters if none are already // specified in the connection URI. if !strings.Contains(dburi, "?") { - dburi += opts.sqlopts + dburi = fmt.Sprintf("%s?%s", dburi, opts.sqlopts.Encode()) } db, err := sql.Open("sqlite3", dburi) @@ -56,6 +70,7 @@ func OpenDB(dburi string, options ...Option) (*sql.DB, error) { // Limit the pool to a single connection. // https://github.com/mattn/go-sqlite3/issues/209 db.SetMaxOpenConns(1) + db.SetMaxIdleConns(1) if err = migrate(db, opts.migrations); err != nil { db.Close() // nolint diff --git a/sqlutil/db_test.go b/sqlutil/db_test.go index e6da51c8e485570aa61c488b7f51ca266bc0dd60..add6dbb6fa5d24a0a4f36694bfca021c935dc3fe 100644 --- a/sqlutil/db_test.go +++ b/sqlutil/db_test.go @@ -3,7 +3,6 @@ package sqlutil import ( "context" "database/sql" - "io/ioutil" "os" "testing" ) @@ -13,7 +12,7 @@ func init() { } func TestOpenDB(t *testing.T) { - dir, err := ioutil.TempDir("", "") + dir, err := os.MkdirTemp("", "") if err != nil { t.Fatal(err) } @@ -44,7 +43,7 @@ func checkTestValue(t *testing.T, db *sql.DB) { } func TestOpenDB_Migrations_MultipleStatements(t *testing.T) { - dir, err := ioutil.TempDir("", "") + dir, err := os.MkdirTemp("", "") if err != nil { t.Fatal(err) } @@ -64,7 +63,7 @@ func TestOpenDB_Migrations_MultipleStatements(t *testing.T) { } func TestOpenDB_Migrations_SingleStatement(t *testing.T) { - dir, err := ioutil.TempDir("", "") + dir, err := os.MkdirTemp("", "") if err != nil { t.Fatal(err) } @@ -86,7 +85,7 @@ func TestOpenDB_Migrations_SingleStatement(t *testing.T) { } func TestOpenDB_Migrations_Versions(t *testing.T) { - dir, err := ioutil.TempDir("", "") + dir, err := os.MkdirTemp("", "") if err != nil { t.Fatal(err) } @@ -114,7 +113,7 @@ func TestOpenDB_Migrations_Versions(t *testing.T) { } func TestOpenDB_Write(t *testing.T) { - dir, err := ioutil.TempDir("", "") + dir, err := os.MkdirTemp("", "") if err != nil { t.Fatal(err) } @@ -143,7 +142,7 @@ func TestOpenDB_Write(t *testing.T) { } func TestOpenDB_Migrations_Legacy(t *testing.T) { - dir, err := ioutil.TempDir("", "") + dir, err := os.MkdirTemp("", "") if err != nil { t.Fatal(err) }