Skip to content

Commit 54958c6

Browse files
committed
examples: add a backup example program
1 parent a6ec13c commit 54958c6

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed

examples/backup/backup.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"database/sql/driver"
7+
"log"
8+
"runtime"
9+
"sync"
10+
"sync/atomic"
11+
"time"
12+
13+
"github.com/tailscale/sqlite"
14+
)
15+
16+
var (
17+
// The way that SQLite3 backups work is that they restart if the database is
18+
// ever updated from a different context than the one backing up, however
19+
// modifications made using the same context as the backup can be observed
20+
// without restarting. The application must just ensure that the connection is
21+
// either performing queries, or performing a backup step at any given time.
22+
// The backup step size can be tuned by the application to appropriately
23+
// share time between the writer and backup operations.
24+
mu sync.Mutex
25+
conn *sql.Conn
26+
27+
inserted atomic.Int64
28+
29+
walMode = func(ctx context.Context, conn driver.ConnPrepareContext) error {
30+
return sqlite.ExecScript(conn.(sqlite.SQLConn), "PRAGMA journal_mode=WAL;")
31+
}
32+
)
33+
34+
func main() {
35+
ctx, cancelAndWait := withCancelWait(context.Background())
36+
db := sql.OpenDB(sqlite.Connector("file:/tmp/example.db", walMode, nil))
37+
defer db.Close()
38+
39+
var err error
40+
conn, err = db.Conn(context.Background())
41+
must(err)
42+
defer conn.Close()
43+
44+
must(initSchema(ctx))
45+
46+
go fill(ctx)
47+
48+
log.Printf("sleeping for 10 seconds to populate the table")
49+
time.Sleep(10 * time.Second)
50+
log.Printf("inserted: %d", inserted.Load())
51+
52+
backup(ctx)
53+
54+
cancelAndWait()
55+
}
56+
57+
func backup(ctx context.Context) {
58+
bdb := sql.OpenDB(sqlite.Connector("file:/tmp/example-backup.db", walMode, nil))
59+
defer bdb.Close()
60+
bConn, err := bdb.Conn(ctx)
61+
must(err)
62+
defer bConn.Close()
63+
64+
log.Printf("backing up")
65+
b, err := sqlite.NewBackup(bConn, "main", conn, "main")
66+
must(err)
67+
68+
var (
69+
more bool = true
70+
remaining int
71+
pageCount int
72+
)
73+
74+
for more {
75+
mu.Lock()
76+
more, remaining, pageCount, err = b.Step(1024)
77+
mu.Unlock()
78+
if err != nil {
79+
// fatal errors are returned by finish too
80+
break
81+
}
82+
log.Printf("remaining=%5d pageCount=%5d (inserted: %5d)", remaining, pageCount, inserted.Load())
83+
time.Sleep(time.Millisecond)
84+
}
85+
log.Printf("backup steps done")
86+
must(b.Finish())
87+
log.Printf("backup finished")
88+
}
89+
90+
func fill(ctx context.Context) {
91+
defer done(ctx)
92+
for alive(ctx) {
93+
mu.Lock()
94+
_, err := conn.ExecContext(ctx, "INSERT INTO foo (data) VALUES ('never gunna back you up, never gunna take you down, never gunna alter schema and hurt you');")
95+
inserted.Add(1)
96+
mu.Unlock()
97+
must(err)
98+
}
99+
}
100+
101+
func initSchema(ctx context.Context) error {
102+
mu.Lock()
103+
defer mu.Unlock()
104+
_, err := conn.ExecContext(ctx, `
105+
CREATE TABLE IF NOT EXISTS foo (
106+
id INTEGER PRIMARY KEY,
107+
data TEXT
108+
);
109+
`)
110+
return err
111+
}
112+
113+
func must(err error) {
114+
_, file, no, _ := runtime.Caller(1)
115+
if err != nil {
116+
log.Fatalf("%s:%d %#v", file, no, err)
117+
}
118+
}
119+
120+
var wgKey = &struct{}{}
121+
122+
type waitCtx struct {
123+
context.Context
124+
wg *sync.WaitGroup
125+
}
126+
127+
func (c *waitCtx) Done() <-chan struct{} {
128+
return c.Context.Done()
129+
}
130+
131+
func (c *waitCtx) Err() error {
132+
return c.Context.Err()
133+
}
134+
135+
func (c *waitCtx) Deadline() (deadline time.Time, ok bool) {
136+
return c.Context.Deadline()
137+
}
138+
139+
func (c *waitCtx) Value(key interface{}) interface{} {
140+
if key == wgKey {
141+
return c.wg
142+
}
143+
return c.Context.Value(key)
144+
}
145+
146+
var _ context.Context = &waitCtx{}
147+
148+
func withWait(ctx context.Context) context.Context {
149+
wg, ok := ctx.Value(wgKey).(*sync.WaitGroup)
150+
if !ok {
151+
wg = &sync.WaitGroup{}
152+
ctx = &waitCtx{ctx, wg}
153+
}
154+
wg.Add(1)
155+
return ctx
156+
}
157+
158+
func alive(ctx context.Context) bool {
159+
select {
160+
case <-ctx.Done():
161+
return false
162+
default:
163+
return true
164+
}
165+
}
166+
167+
func wait(ctx context.Context) {
168+
ctx.Value(wgKey).(*sync.WaitGroup).Wait()
169+
}
170+
171+
func done(ctx context.Context) {
172+
ctx.Value(wgKey).(*sync.WaitGroup).Done()
173+
}
174+
175+
func withCancelWait(ctx context.Context) (context.Context, context.CancelFunc) {
176+
ctx, cancel := context.WithCancel(withWait(ctx))
177+
return ctx, func() {
178+
cancel()
179+
wait(ctx)
180+
}
181+
}

0 commit comments

Comments
 (0)