Skip to content

Commit 15e15a3

Browse files
committed
all: export Checkpoint and BusyTimeout
Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
1 parent 86f2bd3 commit 15e15a3

File tree

4 files changed

+120
-0
lines changed

4 files changed

+120
-0
lines changed

cgosqlite/cgosqlite.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ func (db *DB) BusyTimeout(d time.Duration) {
118118
C.sqlite3_busy_timeout(db.db, C.int(d/1e6))
119119
}
120120

121+
func (db *DB) Checkpoint(dbName string, mode sqliteh.Checkpoint) (int, int, error) {
122+
var cDB *C.char
123+
if dbName != "" {
124+
// Docs say: "If parameter zDb is NULL or points to a zero length string",
125+
// so they are equivalent here.
126+
cDB = C.CString(dbName)
127+
defer C.free(unsafe.Pointer(cDB))
128+
}
129+
var nLog, nCkpt C.int
130+
res := C.sqlite3_wal_checkpoint_v2(db.db, cDB, C.int(mode), &nLog, &nCkpt)
131+
return int(nLog), int(nCkpt), errCode(res)
132+
}
133+
121134
func (db *DB) Prepare(query string, prepFlags sqliteh.PrepareFlags) (stmt sqliteh.Stmt, remainingQuery string, err error) {
122135
csql := C.CString(query)
123136
defer C.free(unsafe.Pointer(csql))

sqlite.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,12 +695,41 @@ func ExecScript(sqlconn SQLConn, queries string) error {
695695
_, err = cstmt.Step()
696696
cstmt.Finalize()
697697
if err != nil {
698+
// TODO(crawshaw): consider checking sqlite3_txn_state
699+
// here and issuing a rollback, incase this script was:
700+
// BEGIN; BAD-SQL; COMMIT;
701+
// So we don't leave the connection open.
698702
return reserr(c.db, "ExecScript", queries, err)
699703
}
700704
}
701705
})
702706
}
703707

708+
// BusyTimeout calls sqlite3_busy_timeout on the underlying connection.
709+
func BusyTimeout(sqlconn SQLConn, d time.Duration) error {
710+
return sqlconn.Raw(func(driverConn interface{}) error {
711+
c, ok := driverConn.(*conn)
712+
if !ok {
713+
return fmt.Errorf("sqlite.BusyTimeout: sql.Conn is not the sqlite driver: %T", driverConn)
714+
}
715+
c.db.BusyTimeout(d)
716+
return nil
717+
})
718+
}
719+
720+
// Checkpoint calls sqlite3_wal_checkpoint_v2 on the underlying connection.
721+
func Checkpoint(sqlconn SQLConn, dbName string, mode sqliteh.Checkpoint) (numFrames, numFramesCheckpointed int, err error) {
722+
err = sqlconn.Raw(func(driverConn interface{}) error {
723+
c, ok := driverConn.(*conn)
724+
if !ok {
725+
return fmt.Errorf("sqlite.BusyTimeout: sql.Conn is not the sqlite driver: %T", driverConn)
726+
}
727+
numFrames, numFramesCheckpointed, err = c.db.Checkpoint(dbName, mode)
728+
return reserr(c.db, "Checkpoint", dbName, err)
729+
})
730+
return numFrames, numFramesCheckpointed, err
731+
}
732+
704733
// WithPersist makes a ctx instruct the sqlite driver to persist a prepared query.
705734
//
706735
// This should be used with recurring queries to avoid constant parsing and

sqlite_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import (
88
"context"
99
"database/sql"
1010
"fmt"
11+
"os"
1112
"runtime"
1213
"strings"
1314
"testing"
1415
"time"
16+
17+
"github.com/tailscale/sqlite/sqliteh"
1518
)
1619

1720
func TestOpenDB(t *testing.T) {
@@ -407,6 +410,9 @@ func TestErrors(t *testing.T) {
407410
if want := `near "NOT": syntax error`; !strings.Contains(err.Error(), want) {
408411
t.Errorf("err=%v, want %q", err, want)
409412
}
413+
if err := ExecScript(conn, "ROLLBACK;"); err != nil { // TODO: make unnecessary?
414+
t.Fatal(err)
415+
}
410416

411417
err = ExecScript(conn, `CREATE TABLE t (c INTEGER PRIMARY KEY);
412418
INSERT INTO t (c) VALUES (1);
@@ -427,6 +433,47 @@ func TestErrors(t *testing.T) {
427433
}
428434
}
429435

436+
func TestCheckpoint(t *testing.T) {
437+
dbFile := t.TempDir() + "/test.db"
438+
db, err := sql.Open("sqlite3", "file:"+dbFile)
439+
if err != nil {
440+
t.Fatal(err)
441+
}
442+
if _, err := db.Exec("PRAGMA journal_mode=WAL"); err != nil {
443+
t.Fatal(err)
444+
}
445+
446+
ctx := context.Background()
447+
conn, err := db.Conn(ctx)
448+
if err != nil {
449+
t.Fatal(err)
450+
}
451+
err = ExecScript(conn, `CREATE TABLE t (c);
452+
INSERT INTO t (c) VALUES (1);
453+
INSERT INTO t (c) VALUES (1);`)
454+
if err != nil {
455+
t.Fatal(err)
456+
}
457+
458+
if fi, err := os.Stat(dbFile + "-wal"); err != nil {
459+
t.Fatal(err)
460+
} else if fi.Size() == 0 {
461+
t.Fatal("WAL is empty")
462+
} else {
463+
t.Logf("WAL is %d bytes", fi.Size())
464+
}
465+
466+
if _, _, err := Checkpoint(conn, "", sqliteh.SQLITE_CHECKPOINT_TRUNCATE); err != nil {
467+
t.Fatal(err)
468+
}
469+
470+
if fi, err := os.Stat(dbFile + "-wal"); err != nil {
471+
t.Fatal(err)
472+
} else if fi.Size() != 0 {
473+
t.Fatal("WAL is not empty")
474+
}
475+
}
476+
430477
func TestTrace(t *testing.T) {
431478
type traceEvent struct {
432479
prepCtx context.Context

sqliteh/sqliteh.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ type DB interface {
4545
// BusyTimeout is sqlite3_busy_timeout.
4646
// https://www.sqlite.org/c3ref/busy_timeout.html
4747
BusyTimeout(time.Duration)
48+
// Checkpoint is sqlite3_wal_checkpoint_v2.
49+
Checkpoint(db string, mode Checkpoint) (numFrames, numFramesCheckpointed int, err error)
4850
}
4951

5052
// Stmt is an sqlite3_stmt* database connection object.
@@ -323,6 +325,35 @@ func (o OpenFlags) String() string {
323325
return string(flags)
324326
}
325327

328+
// Checkpoint is a WAL checkpoint mode.
329+
// It is used by sqlite3_wal_checkpoint_v2.
330+
//
331+
// https://sqlite.org/c3ref/wal_checkpoint_v2.html
332+
type Checkpoint int
333+
334+
const (
335+
SQLITE_CHECKPOINT_PASSIVE Checkpoint = 0
336+
SQLITE_CHECKPOINT_FULL Checkpoint = 1
337+
SQLITE_CHECKPOINT_RESTART Checkpoint = 2
338+
SQLITE_CHECKPOINT_TRUNCATE Checkpoint = 3
339+
)
340+
341+
func (mode Checkpoint) String() string {
342+
switch mode {
343+
default:
344+
var buf [20]byte
345+
return "SQLITE_CHECKPOINT_UNKNOWN(" + string(itoa(buf[:], int64(mode))) + ")"
346+
case SQLITE_CHECKPOINT_PASSIVE:
347+
return "SQLITE_CHECKPOINT_PASSIVE"
348+
case SQLITE_CHECKPOINT_FULL:
349+
return "SQLITE_CHECKPOINT_FULL"
350+
case SQLITE_CHECKPOINT_RESTART:
351+
return "SQLITE_CHECKPOINT_RESTART"
352+
case SQLITE_CHECKPOINT_TRUNCATE:
353+
return "SQLITE_CHECKPOINT_TRUNCATE"
354+
}
355+
}
356+
326357
// ErrCode is an SQLite error code as a Go error.
327358
// It must not be one of the status codes SQLITE_OK, SQLITE_ROW, or SQLITE_DONE.
328359
type ErrCode Code

0 commit comments

Comments
 (0)