Skip to content

Commit 0fce88d

Browse files
Mura Litypeless
authored andcommitted
Add support for sqlite3_unlock_notify
1 parent 0a88db3 commit 0fce88d

File tree

5 files changed

+262
-5
lines changed

5 files changed

+262
-5
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ env:
1212
matrix:
1313
- GOTAGS=
1414
- GOTAGS=libsqlite3
15-
- GOTAGS="sqlite_allow_uri_authority sqlite_app_armor sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_userauth sqlite_vacuum_incr sqlite_vtable"
15+
- GOTAGS="sqlite_allow_uri_authority sqlite_app_armor sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_userauth sqlite_vacuum_incr sqlite_vtable sqlite_unlock_notify"
1616
- GOTAGS=sqlite_vacuum_full
1717

1818
go:

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: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
extern void unlock_notify_callback(void *, int);
11+
12+
void _unlock_notify_callback(void *arg, int argc)
13+
{
14+
unlock_notify_callback(arg, argc);
15+
}
16+
17+
int
18+
sqlite3_step_blocking(sqlite3_stmt *stmt)
19+
{
20+
int rv;
21+
sqlite3* db;
22+
23+
db = sqlite3_db_handle(stmt);
24+
for (;;) {
25+
rv = sqlite3_step(stmt);
26+
if (rv != SQLITE_LOCKED) {
27+
break;
28+
}
29+
if (sqlite3_extended_errcode(db) != SQLITE_LOCKED_SHAREDCACHE) {
30+
break;
31+
}
32+
rv = unlock_notify_wait(db);
33+
if (rv != SQLITE_OK) {
34+
break;
35+
}
36+
sqlite3_reset(stmt);
37+
}
38+
39+
return rv;
40+
}
41+
42+
int
43+
_sqlite3_step_blocking(sqlite3_stmt* stmt, long long* rowid, long long* changes)
44+
{
45+
int rv;
46+
sqlite3* db;
47+
48+
db = sqlite3_db_handle(stmt);
49+
for (;;) {
50+
rv = sqlite3_step(stmt);
51+
if (rv!=SQLITE_LOCKED) {
52+
break;
53+
}
54+
if (sqlite3_extended_errcode(db) != SQLITE_LOCKED_SHAREDCACHE) {
55+
break;
56+
}
57+
rv = unlock_notify_wait(db);
58+
if (rv != SQLITE_OK) {
59+
break;
60+
}
61+
sqlite3_reset(stmt);
62+
}
63+
64+
*rowid = (long long) sqlite3_last_insert_rowid(db);
65+
*changes = (long long) sqlite3_changes(db);
66+
return rv;
67+
}
68+
69+
int
70+
sqlite3_prepare_v2_blocking(sqlite3 *db, const char *zSql, int nBytes, sqlite3_stmt **ppStmt, const char **pzTail)
71+
{
72+
int rv;
73+
74+
for (;;) {
75+
rv = sqlite3_prepare_v2(db, zSql, nBytes, ppStmt, pzTail);
76+
if (rv!=SQLITE_LOCKED) {
77+
break;
78+
}
79+
if (sqlite3_extended_errcode(db) != SQLITE_LOCKED_SHAREDCACHE) {
80+
break;
81+
}
82+
rv = unlock_notify_wait(db);
83+
if (rv != SQLITE_OK) {
84+
break;
85+
}
86+
}
87+
88+
return rv;
89+
}
90+
#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+
}

0 commit comments

Comments
 (0)