mirror of
https://github.com/glebarez/go-sqlite.git
synced 2025-10-05 15:56:52 +08:00
Merge branch 'persistent_pragma_configs' into 'master'
Persist pragma configurations via url parameter Closes #37 See merge request cznic/sqlite!27
This commit is contained in:
1
AUTHORS
1
AUTHORS
@@ -12,6 +12,7 @@ Dan Peterson <danp@danp.net>
|
|||||||
Davsk Ltd Co <skinner.david@gmail.com>
|
Davsk Ltd Co <skinner.david@gmail.com>
|
||||||
Jaap Aarts <jaap.aarts1@gmail.com>
|
Jaap Aarts <jaap.aarts1@gmail.com>
|
||||||
Jan Mercl <0xjnml@gmail.com>
|
Jan Mercl <0xjnml@gmail.com>
|
||||||
|
Logan Snow <logansnow@protonmail.com>
|
||||||
Ross Light <ross@zombiezen.com>
|
Ross Light <ross@zombiezen.com>
|
||||||
Steffen Butzer <steffen(dot)butzer@outlook.com>
|
Steffen Butzer <steffen(dot)butzer@outlook.com>
|
||||||
Saed SayedAhmed <saadmtsa@gmail.com>
|
Saed SayedAhmed <saadmtsa@gmail.com>
|
||||||
|
@@ -11,6 +11,7 @@ Dan Peterson <danp@danp.net>
|
|||||||
David Skinner <skinner.david@gmail.com>
|
David Skinner <skinner.david@gmail.com>
|
||||||
Jaap Aarts <jaap.aarts1@gmail.com>
|
Jaap Aarts <jaap.aarts1@gmail.com>
|
||||||
Jan Mercl <0xjnml@gmail.com>
|
Jan Mercl <0xjnml@gmail.com>
|
||||||
|
Logan Snow <logansnow@protonmail.com>
|
||||||
Ross Light <ross@zombiezen.com>
|
Ross Light <ross@zombiezen.com>
|
||||||
Steffen Butzer <steffen(dot)butzer@outlook.com>
|
Steffen Butzer <steffen(dot)butzer@outlook.com>
|
||||||
Yaacov Akiba Slama <ya@slamail.org>
|
Yaacov Akiba Slama <ya@slamail.org>
|
||||||
|
163
all_test.go
163
all_test.go
@@ -8,10 +8,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -1574,6 +1576,167 @@ CREATE TABLE IF NOT EXISTS loginst (
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://gitlab.com/cznic/sqlite/-/issues/37
|
||||||
|
func TestPersistPragma(t *testing.T) {
|
||||||
|
if err := emptyDir(tempDir); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Chdir(wd)
|
||||||
|
|
||||||
|
if err := os.Chdir(tempDir); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pragmas := []pragmaCfg{
|
||||||
|
{"foreign_keys", "on", int64(1)},
|
||||||
|
{"analysis_limit", "1000", int64(1000)},
|
||||||
|
{"application_id", "214", int64(214)},
|
||||||
|
{"encoding", "'UTF-16le'", "UTF-16le"}}
|
||||||
|
|
||||||
|
if err := testPragmas("x.sqlite", "x.sqlite", pragmas); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := testPragmas("file::memory:", "", pragmas); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := testPragmas(":memory:", "", pragmas); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type pragmaCfg struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
expected interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPragmas(name, diskFile string, pragmas []pragmaCfg) error {
|
||||||
|
if diskFile != "" {
|
||||||
|
os.Remove(diskFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
q := url.Values{}
|
||||||
|
for _, pragma := range pragmas {
|
||||||
|
q.Add("_pragma", pragma.name+"="+pragma.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
dsn := name + "?" + q.Encode()
|
||||||
|
db, err := sql.Open(driverName, dsn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SetMaxOpenConns(1)
|
||||||
|
|
||||||
|
if err := checkPragmas(db, pragmas); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := db.Conn(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill the connection to spawn a new one. Pragma configs should persist
|
||||||
|
c.Raw(func(interface{}) error { return driver.ErrBadConn })
|
||||||
|
|
||||||
|
if err := checkPragmas(db, pragmas); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if diskFile == "" {
|
||||||
|
// Make sure in memory databases aren't being written to disk
|
||||||
|
return testInMemory(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPragmas(db *sql.DB, pragmas []pragmaCfg) error {
|
||||||
|
for _, pragma := range pragmas {
|
||||||
|
row := db.QueryRow(`PRAGMA ` + pragma.name)
|
||||||
|
|
||||||
|
var result interface{}
|
||||||
|
if err := row.Scan(&result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if result != pragma.expected {
|
||||||
|
return fmt.Errorf("expected PRAGMA %s to return %v but got %v", pragma.name, pragma.expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInMemory(t *testing.T) {
|
||||||
|
if err := emptyDir(tempDir); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Chdir(wd)
|
||||||
|
|
||||||
|
if err := os.Chdir(tempDir); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testMemoryPath(":memory:"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := testMemoryPath("file::memory:"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This parameter should be ignored
|
||||||
|
q := url.Values{}
|
||||||
|
q.Add("mode", "readonly")
|
||||||
|
if err := testMemoryPath(":memory:?" + q.Encode()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMemoryPath(mPath string) error {
|
||||||
|
db, err := sql.Open(driverName, mPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
return testInMemory(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInMemory(db *sql.DB) error {
|
||||||
|
_, err := db.Exec(`
|
||||||
|
create table in_memory_test(i int, f double);
|
||||||
|
insert into in_memory_test values(12, 3.14);
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := ioutil.ReadDir("./")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if strings.Contains(file.Name(), "memory") {
|
||||||
|
return fmt.Errorf("file was created for in memory database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func emptyDir(s string) error {
|
func emptyDir(s string) error {
|
||||||
m, err := filepath.Glob(filepath.FromSlash(s + "/*"))
|
m, err := filepath.Glob(filepath.FromSlash(s + "/*"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
38
sqlite.go
38
sqlite.go
@@ -15,6 +15,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -728,10 +729,23 @@ type conn struct {
|
|||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConn(name string) (*conn, error) {
|
func newConn(dsn string) (*conn, error) {
|
||||||
|
var query string
|
||||||
|
|
||||||
|
// Parse the query parameters from the dsn and them from the dsn if not prefixed by file:
|
||||||
|
// https://github.com/mattn/go-sqlite3/blob/3392062c729d77820afc1f5cae3427f0de39e954/sqlite3.go#L1046
|
||||||
|
// https://github.com/mattn/go-sqlite3/blob/3392062c729d77820afc1f5cae3427f0de39e954/sqlite3.go#L1383
|
||||||
|
pos := strings.IndexRune(dsn, '?')
|
||||||
|
if pos >= 1 {
|
||||||
|
query = dsn[pos+1:]
|
||||||
|
if !strings.HasPrefix(dsn, "file:") {
|
||||||
|
dsn = dsn[:pos]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c := &conn{tls: libc.NewTLS()}
|
c := &conn{tls: libc.NewTLS()}
|
||||||
db, err := c.openV2(
|
db, err := c.openV2(
|
||||||
name,
|
dsn,
|
||||||
sqlite3.SQLITE_OPEN_READWRITE|sqlite3.SQLITE_OPEN_CREATE|
|
sqlite3.SQLITE_OPEN_READWRITE|sqlite3.SQLITE_OPEN_CREATE|
|
||||||
sqlite3.SQLITE_OPEN_FULLMUTEX|
|
sqlite3.SQLITE_OPEN_FULLMUTEX|
|
||||||
sqlite3.SQLITE_OPEN_URI,
|
sqlite3.SQLITE_OPEN_URI,
|
||||||
@@ -746,9 +760,29 @@ func newConn(name string) (*conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = applyPragmas(c, query); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyPragmas(c *conn, query string) error {
|
||||||
|
q, err := url.ParseQuery(query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, v := range q["_pragma"] {
|
||||||
|
cmd := "pragma " + v
|
||||||
|
_, err := c.exec(context.Background(), cmd, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
|
// const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
|
||||||
func (c *conn) columnBlob(pstmt uintptr, iCol int) (v []byte, err error) {
|
func (c *conn) columnBlob(pstmt uintptr, iCol int) (v []byte, err error) {
|
||||||
p := sqlite3.Xsqlite3_column_blob(c.tls, pstmt, int32(iCol))
|
p := sqlite3.Xsqlite3_column_blob(c.tls, pstmt, int32(iCol))
|
||||||
|
Reference in New Issue
Block a user