package sqlite import ( "context" "database/sql" "strings" _ "github.com/mattn/go-sqlite3" ) // https://github.com/mattn/go-sqlite3#connection-string var dsnOpts = []string{ "_foreign_keys=1", "_journal_mode=WAL", "_synchronous=NORMAL", "_busy_timeout=5000", "_txlock=immediate", } func Open(dbPath string) (*sql.DB, error) { if err := initFile(dbPath); err != nil { return nil, err } return sql.Open("sqlite3", dbPath+"?"+strings.Join(dsnOpts, "&")) } // auto_vacuum only sticks if it is set before the file's first write, which // switching to wal is. setting it takes the write lock even when it's already // set, so it can't live in the dsn, where every new pool connection would wait // out the busy timeout behind whatever transaction is open, and it's skipped // for files that already have content func initFile(dbPath string) error { db, err := sql.Open("sqlite3", dbPath+"?_busy_timeout=5000") if err != nil { return err } defer db.Close() conn, err := db.Conn(context.Background()) if err != nil { return err } defer conn.Close() var pages int if err := conn.QueryRowContext(context.Background(), `pragma page_count`).Scan(&pages); err != nil { return err } if pages > 0 { return nil } for _, pragma := range []string{`pragma auto_vacuum = incremental`, `pragma journal_mode = wal`} { if _, err := conn.ExecContext(context.Background(), pragma); err != nil { return err } } return nil }