mirror of
https://github.com/glebarez/go-sqlite.git
synced 2025-10-04 23:42:40 +08:00
Add _txlock DSN parameter to customize BEGIN
This commit is contained in:

committed by
glebarez

parent
5660418392
commit
870db7651a
@@ -13,6 +13,7 @@ Gleb Sakhnov <gleb.sakhnov@gmail.com>
|
||||
Jaap Aarts <jaap.aarts1@gmail.com>
|
||||
Jan Mercl <0xjnml@gmail.com>
|
||||
Logan Snow <logansnow@protonmail.com>
|
||||
Matthew Gabeler-Lee <fastcat@gmail.com>
|
||||
Ross Light <ross@zombiezen.com>
|
||||
Steffen Butzer <steffen(dot)butzer@outlook.com>
|
||||
Yaacov Akiba Slama <ya@slamail.org>
|
||||
|
67
all_test.go
67
all_test.go
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
"modernc.org/libc"
|
||||
"modernc.org/mathutil"
|
||||
sqlite3 "modernc.org/sqlite/lib"
|
||||
)
|
||||
|
||||
func caller(s string, va ...interface{}) {
|
||||
@@ -2137,3 +2138,69 @@ func TestConstraintUniqueError(t *testing.T) {
|
||||
t.Fatalf("got error string %q, want %q", errs, want)
|
||||
}
|
||||
}
|
||||
|
||||
// https://gitlab.com/cznic/sqlite/-/issues/92
|
||||
func TestBeginMode(t *testing.T) {
|
||||
tempDir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
os.RemoveAll(tempDir)
|
||||
}()
|
||||
|
||||
tests := []struct {
|
||||
mode string
|
||||
want int32
|
||||
}{
|
||||
{"deferred", sqlite3.SQLITE_TXN_NONE},
|
||||
{"immediate", sqlite3.SQLITE_TXN_WRITE},
|
||||
// TODO: how to verify "exclusive" is working differently from immediate,
|
||||
// short of concurrently trying to open the database again? This is only
|
||||
// different in non-WAL journal modes.
|
||||
{"exclusive", sqlite3.SQLITE_TXN_WRITE},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
for _, jm := range []string{"delete", "wal"} {
|
||||
jm := jm
|
||||
t.Run(jm+"/"+tt.mode, func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
qs := fmt.Sprintf("?_txlock=%s&_pragma=journal_mode(%s)", tt.mode, jm)
|
||||
db, err := sql.Open("sqlite", filepath.Join(tempDir, fmt.Sprintf("testbeginmode-%s.sqlite", tt.mode))+qs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
connection, err := db.Conn(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open connection: %v", err)
|
||||
}
|
||||
|
||||
tx, err := connection.BeginTx(context.Background(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to begin transaction: %v", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if err := connection.Raw(func(driverConn interface{}) error {
|
||||
p, err := libc.CString("main")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := driverConn.(*conn)
|
||||
defer c.free(p)
|
||||
got := sqlite3.Xsqlite3_txn_state(c.tls, c.db, p)
|
||||
if got != tt.want {
|
||||
return fmt.Errorf("in mode %s, got txn state %d, want %d", tt.mode, got, tt.want)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("Failed to check txn state: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
38
sqlite.go
38
sqlite.go
@@ -726,7 +726,13 @@ type tx struct {
|
||||
|
||||
func newTx(c *conn) (*tx, error) {
|
||||
r := &tx{c: c}
|
||||
if err := r.exec(context.Background(), "begin"); err != nil {
|
||||
var sql string
|
||||
if c.beginMode != "" {
|
||||
sql = "begin " + c.beginMode
|
||||
} else {
|
||||
sql = "begin"
|
||||
}
|
||||
if err := r.exec(context.Background(), sql); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -784,6 +790,7 @@ type conn struct {
|
||||
sync.Mutex
|
||||
|
||||
writeTimeFormat string
|
||||
beginMode string
|
||||
}
|
||||
|
||||
func newConn(dsn string) (*conn, error) {
|
||||
@@ -866,6 +873,14 @@ func applyQueryParams(c *conn, query string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v := q.Get("_txlock"); v != "" {
|
||||
lower := strings.ToLower(v)
|
||||
if lower != "deferred" && lower != "immediate" && lower != "exclusive" {
|
||||
return fmt.Errorf("unknown _txlock %q", v)
|
||||
}
|
||||
c.beginMode = v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1445,6 +1460,27 @@ func newDriver() *Driver { return &Driver{} }
|
||||
// efficient re-use.
|
||||
//
|
||||
// The returned connection is only used by one goroutine at a time.
|
||||
//
|
||||
// If name contains a '?', what follows is treated as a query string. This
|
||||
// driver supports the following query parameters:
|
||||
//
|
||||
// _pragma: Each value will be run as a "PRAGMA ..." statement (with the PRAGMA
|
||||
// keyword added for you). May be specified more than once. Example:
|
||||
// "_pragma=foreign_keys(1)" will enable foreign key enforcement. More
|
||||
// information on supported PRAGMAs is available from the SQLite documentation:
|
||||
// https://www.sqlite.org/pragma.html
|
||||
//
|
||||
// _time_format: The name of a format to use when writing time values to the
|
||||
// database. Currently the only supported value is "sqlite", which corresponds
|
||||
// to format 7 from https://www.sqlite.org/lang_datefunc.html#time_values,
|
||||
// including the timezone specifier. If this parameter is not specified, then
|
||||
// the default String() format will be used.
|
||||
//
|
||||
// _txlock: The locking behavior to use when beginning a transaction. May be
|
||||
// "deferred", "immediate", or "exclusive" (case insensitive). The default is to
|
||||
// not specify one, which SQLite maps to "deferred". More information is
|
||||
// available at
|
||||
// https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
|
||||
func (d *Driver) Open(name string) (driver.Conn, error) {
|
||||
if LogSqlStatements {
|
||||
log.Println("new connection")
|
||||
|
Reference in New Issue
Block a user