Skip to content

Commit 6903ee1

Browse files
typelessMura Li
authored andcommitted
Add support for sqlite3_unlock_notify
1 parent 43064d7 commit 6903ee1

File tree

5 files changed

+260
-64
lines changed

5 files changed

+260
-64
lines changed

sqlite3.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,38 @@ _sqlite3_exec(sqlite3* db, const char* pcmd, long long* rowid, long long* change
7878
return rv;
7979
}
8080
81+
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
82+
extern int sqlite3_step_blocking(sqlite3_stmt *stmt);
83+
extern int _sqlite3_step_blocking(sqlite3_stmt* stmt, long long* rowid, long long* changes);
84+
extern int sqlite3_prepare_v2_blocking(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail);
85+
86+
static int
87+
sqlite3_step_internal(sqlite3_stmt *stmt)
88+
{
89+
return sqlite3_step_blocking(stmt);
90+
}
91+
92+
static int
93+
_sqlite3_step_internal(sqlite3_stmt* stmt, long long* rowid, long long* changes)
94+
{
95+
return _sqlite3_step_blocking(stmt, rowid, changes);
96+
}
97+
8198
static int
82-
_sqlite3_step(sqlite3_stmt* stmt, long long* rowid, long long* changes)
99+
sqlite3_prepare_v2_internal(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail)
100+
{
101+
return sqlite3_prepare_v2_blocking(db, zSql, nBytes, ppStmt, pzTail);
102+
}
103+
104+
#else
105+
static int
106+
sqlite3_step_internal(sqlite3_stmt *stmt)
107+
{
108+
return sqlite3_step(stmt);
109+
}
110+
111+
static int
112+
_sqlite3_step_internal(sqlite3_stmt* stmt, long long* rowid, long long* changes)
83113
{
84114
int rv = sqlite3_step(stmt);
85115
sqlite3* db = sqlite3_db_handle(stmt);
@@ -88,6 +118,13 @@ _sqlite3_step(sqlite3_stmt* stmt, long long* rowid, long long* changes)
88118
return rv;
89119
}
90120
121+
static int
122+
sqlite3_prepare_v2_internal(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail)
123+
{
124+
return sqlite3_prepare_v2(db, zSql, nBytes, ppStmt, pzTail);
125+
}
126+
#endif
127+
91128
void _sqlite3_result_text(sqlite3_context* ctx, const char* s) {
92129
sqlite3_result_text(ctx, s, -1, &free);
93130
}
@@ -1637,7 +1674,7 @@ func (c *SQLiteConn) prepare(ctx context.Context, query string) (driver.Stmt, er
16371674
defer C.free(unsafe.Pointer(pquery))
16381675
var s *C.sqlite3_stmt
16391676
var tail *C.char
1640-
rv := C.sqlite3_prepare_v2(c.db, pquery, -1, &s, &tail)
1677+
rv := C.sqlite3_prepare_v2_internal(c.db, pquery, -1, &s, &tail)
16411678
if rv != C.SQLITE_OK {
16421679
return nil, c.lastError()
16431680
}
@@ -1871,7 +1908,7 @@ func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result
18711908
}
18721909

18731910
var rowid, changes C.longlong
1874-
rv := C._sqlite3_step(s.s, &rowid, &changes)
1911+
rv := C._sqlite3_step_internal(s.s, &rowid, &changes)
18751912
if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE {
18761913
err := s.c.lastError()
18771914
C.sqlite3_reset(s.s)
@@ -1943,7 +1980,7 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
19431980
if rc.s.closed {
19441981
return io.EOF
19451982
}
1946-
rv := C.sqlite3_step(rc.s.s)
1983+
rv := C.sqlite3_step_internal(rc.s.s)
19471984
if rv == C.SQLITE_DONE {
19481985
return io.EOF
19491986
}

sqlite3_opt_unlock_notify.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (C) 2018 Yasuhiro Matsumoto <[email protected]>.
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file.
5+
6+
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
7+
#include <sqlite3-binding.h>
8+
9+
extern int unlock_notify_wait(sqlite3 *db);
10+
11+
void _unlock_notify_callback(void *arg, int argc)
12+
{
13+
extern void unlock_notify_callback(void *, int);
14+
unlock_notify_callback(arg, argc);
15+
}
16+
17+
int
18+
sqlite3_step_blocking(sqlite3_stmt *stmt)
19+
{
20+
int rv;
21+
sqlite3* db = sqlite3_db_handle(stmt);
22+
23+
for (;;) {
24+
rv = sqlite3_step(stmt);
25+
if (rv != SQLITE_LOCKED) {
26+
break;
27+
}
28+
if (sqlite3_extended_errcode(db) != SQLITE_LOCKED_SHAREDCACHE) {
29+
break;
30+
}
31+
rv = unlock_notify_wait(db);
32+
if (rv != SQLITE_OK) {
33+
break;
34+
}
35+
sqlite3_reset(stmt);
36+
}
37+
38+
return rv;
39+
}
40+
41+
int
42+
_sqlite3_step_blocking(sqlite3_stmt* stmt, long long* rowid, long long* changes)
43+
{
44+
int rv;
45+
sqlite3* db;
46+
47+
for (;;) {
48+
rv = sqlite3_step(stmt);
49+
if (rv!=SQLITE_LOCKED) {
50+
break;
51+
}
52+
if (sqlite3_extended_errcode(db) != SQLITE_LOCKED_SHAREDCACHE) {
53+
break;
54+
}
55+
rv = unlock_notify_wait(db);
56+
if (rv != SQLITE_OK) {
57+
break;
58+
}
59+
sqlite3_reset(stmt);
60+
}
61+
62+
db = sqlite3_db_handle(stmt);
63+
*rowid = (long long) sqlite3_last_insert_rowid(db);
64+
*changes = (long long) sqlite3_changes(db);
65+
return rv;
66+
}
67+
68+
int
69+
sqlite3_prepare_v2_blocking(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail)
70+
{
71+
int rv;
72+
73+
for (;;) {
74+
rv = sqlite3_prepare_v2(db, zSql, nBytes, ppStmt, pzTail);
75+
if (rv!=SQLITE_LOCKED) {
76+
break;
77+
}
78+
if (sqlite3_extended_errcode(db) != SQLITE_LOCKED_SHAREDCACHE) {
79+
break;
80+
}
81+
rv = unlock_notify_wait(db);
82+
if (rv != SQLITE_OK) {
83+
break;
84+
}
85+
}
86+
87+
return rv;
88+
}
89+
#endif

sqlite3_opt_unlock_notify.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (C) 2018 Yasuhiro Matsumoto <[email protected]>.
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file.
5+
6+
// +build cgo
7+
// +build sqlite_unlock_notify
8+
9+
package sqlite3
10+
11+
/*
12+
#cgo CFLAGS: -DSQLITE_ENABLE_UNLOCK_NOTIFY
13+
14+
#include <sqlite3-binding.h>
15+
16+
extern void _unlock_notify_callback(void *arg, int argc);
17+
*/
18+
import "C"
19+
import (
20+
"sync"
21+
"unsafe"
22+
)
23+
24+
type unlockNotification struct {
25+
notify chan struct{}
26+
lock sync.Mutex
27+
}
28+
29+
//export unlock_notify_callback
30+
func unlock_notify_callback(pargv unsafe.Pointer, argc C.int) {
31+
argv := *(*uintptr)(pargv)
32+
v := (*[1 << 30]uintptr)(unsafe.Pointer(argv))
33+
for i := 0; i < int(argc); i++ {
34+
un := lookupHandle(v[i]).(unlockNotification)
35+
un.notify <- struct{}{}
36+
}
37+
}
38+
39+
var notifyMutex sync.Mutex
40+
41+
//export unlock_notify_wait
42+
func unlock_notify_wait(db *C.sqlite3) C.int {
43+
var un unlockNotification
44+
un.notify = make(chan struct{})
45+
defer close(un.notify)
46+
47+
argv := [1]uintptr{newHandle(nil, un)}
48+
if rv := C.sqlite3_unlock_notify(db, (*[0]byte)(C._unlock_notify_callback), unsafe.Pointer(&argv)); rv != C.SQLITE_OK {
49+
return rv
50+
}
51+
<-un.notify
52+
return C.SQLITE_OK
53+
}

sqlite3_opt_unlock_notify_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (C) 2018 Yasuhiro Matsumoto <[email protected]>.
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file.
5+
6+
// +build sqlite_unlock_notify
7+
8+
package sqlite3
9+
10+
import (
11+
"database/sql"
12+
"fmt"
13+
"os"
14+
"sync"
15+
"testing"
16+
"time"
17+
)
18+
19+
func TestTableLockedError(t *testing.T) {
20+
tempFilename := TempFilename(t)
21+
defer os.Remove(tempFilename)
22+
dsn := fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d", tempFilename, 500)
23+
db, err := sql.Open("sqlite3", dsn)
24+
if err != nil {
25+
t.Fatal("Failed to open database:", err)
26+
}
27+
defer db.Close()
28+
29+
_, err = db.Exec("CREATE TABLE foo(id INTEGER, status INTEGER)")
30+
if err != nil {
31+
t.Fatal("Failed to create table:", err)
32+
}
33+
34+
tx, err := db.Begin()
35+
if err != nil {
36+
t.Fatal("Failed to begin transaction:", err)
37+
}
38+
39+
_, err = tx.Exec("INSERT INTO foo(id, status) VALUES(1, 100)")
40+
if err != nil {
41+
t.Fatal("Failed to insert null:", err)
42+
}
43+
44+
_, err = tx.Exec("UPDATE foo SET status = 200 WHERE id = 1")
45+
if err != nil {
46+
t.Fatal("Failed to update table:", err)
47+
}
48+
49+
wg := sync.WaitGroup{}
50+
wg.Add(1)
51+
timer := time.NewTimer(500 * time.Millisecond)
52+
go func() {
53+
<-timer.C
54+
err := tx.Commit()
55+
if err != nil {
56+
t.Fatal("Failed to commit transaction:", err)
57+
}
58+
wg.Done()
59+
}()
60+
61+
rows, err := db.Query("SELECT count(*) from foo")
62+
if err != nil {
63+
t.Fatal("Unable to query foo table:", err)
64+
}
65+
66+
if rows.Next() {
67+
var count int
68+
if err := rows.Scan(&count); err != nil {
69+
t.Fatal("Failed to Scan rows", err)
70+
}
71+
}
72+
if err := rows.Err(); err != nil {
73+
t.Fatal("Failed at the call to Next:", err)
74+
}
75+
wg.Wait()
76+
77+
}

sqlite3_test.go

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -778,66 +778,6 @@ func TestTransaction(t *testing.T) {
778778
}
779779
}
780780

781-
func TestTableLockedError(t *testing.T) {
782-
tempFilename := TempFilename(t)
783-
defer os.Remove(tempFilename)
784-
dsn := fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d", tempFilename, 500)
785-
db, err := sql.Open("sqlite3", dsn)
786-
if err != nil {
787-
t.Fatal("Failed to open database:", err)
788-
}
789-
defer db.Close()
790-
791-
_, err = db.Exec("CREATE TABLE foo(id INTEGER, status INTEGER)")
792-
if err != nil {
793-
t.Fatal("Failed to create table:", err)
794-
}
795-
796-
tx, err := db.Begin()
797-
if err != nil {
798-
t.Fatal("Failed to begin transaction:", err)
799-
}
800-
801-
_, err = tx.Exec("INSERT INTO foo(id, status) VALUES(1, 100)")
802-
if err != nil {
803-
t.Fatal("Failed to insert null:", err)
804-
}
805-
806-
_, err = tx.Exec("UPDATE foo SET status = 200 WHERE id = 1")
807-
if err != nil {
808-
t.Fatal("Failed to update table:", err)
809-
}
810-
811-
wg := sync.WaitGroup{}
812-
wg.Add(1)
813-
timer := time.NewTimer(500 * time.Millisecond)
814-
go func() {
815-
<-timer.C
816-
err := tx.Commit()
817-
if err != nil {
818-
t.Fatal("Failed to commit transaction:", err)
819-
}
820-
wg.Done()
821-
}()
822-
823-
rows, err := db.Query("SELECT count(*) from foo")
824-
if err != nil {
825-
t.Fatal("Unable to query foo table:", err)
826-
}
827-
828-
if rows.Next() {
829-
var count int
830-
if err := rows.Scan(&count); err != nil {
831-
t.Fatal("Failed to Scan rows", err)
832-
}
833-
}
834-
if err := rows.Err(); err != nil {
835-
t.Fatal("Failed at the call to Next:", err)
836-
}
837-
wg.Wait()
838-
839-
}
840-
841781
func TestWAL(t *testing.T) {
842782
tempFilename := TempFilename(t)
843783
defer os.Remove(tempFilename)

0 commit comments

Comments
 (0)