-
Notifications
You must be signed in to change notification settings - Fork 2
/
sqlite3.go
101 lines (84 loc) · 2.72 KB
/
sqlite3.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package backups
import (
"database/sql"
"path/filepath"
"time"
sqlite3 "github.com/mattn/go-sqlite3"
"github.com/rotationalio/ensign/pkg/utils/sqlite"
"github.com/rs/zerolog/log"
)
const (
SQLite3Main = "main"
PagesPerStep = 5
StepSleep = 50 * time.Millisecond
DefaultDBName = "backup.db"
)
// SQLite3 implements a single sqlite3 backup that uses the online backup of a running
// database mechanism to copy the db over to a second sqlite3 database at the temporary
// directory location.
type SQLite3 struct {
DB *sqlite.Conn
Name string
}
var _ Backup = &SQLite3{}
// Backup executes the sqlite3 backup strategy.
func (s *SQLite3) Backup(tmpdir string) (err error) {
var (
dstDB *sql.DB
dstConn *sqlite.Conn
)
// Open a second sqlite3 database at the backup location.
if dstDB, dstConn, err = s.OpenDestDB(tmpdir); err != nil {
return err
}
// Ensure the database connection is closed when the backup is complete; this will
// also close the underlying sqlite3 connection.
defer dstDB.Close()
// Create the backup manager into the destination db from the src connection.
// NOTE: backup.Finish() MUST be called to prevent panics.
var backup *sqlite3.SQLiteBackup
if backup, err = dstConn.Backup(SQLite3Main, s.DB, SQLite3Main); err != nil {
return err
}
// Execute the backup copying the specified number of pages at each step then
// sleeping to allow concurrent transactions to acquire write locks. This will
// increase the amount of backup time but preserve normal operations. This means
// that backups will be most successful during low-volume times.
var isDone bool
for !isDone {
// Backing up a smaller number of pages per step is the most effective way of
// doing online backups and also allow write transactions to make progress.
if isDone, err = backup.Step(PagesPerStep); err != nil {
backup.Finish()
return err
}
log.Debug().
Int("remaining", backup.Remaining()).
Int("page_count", backup.PageCount()).
Msg("sqlite3 backup step")
// This sleep allows other transactions to write during backups.
time.Sleep(StepSleep)
}
return backup.Finish()
}
func (s *SQLite3) OpenDestDB(tmpdir string) (db *sql.DB, conn *sqlite.Conn, err error) {
var path string
if s.Name != "" {
path = filepath.Join(tmpdir, s.Name)
} else {
path = filepath.Join(tmpdir, DefaultDBName)
}
if db, err = sql.Open(sqlite.DriverName, path); err != nil {
return nil, nil, err
}
// Ping the database in order to establish a connection
if err = db.Ping(); err != nil {
return nil, nil, err
}
// Get the last database connection from the sqlite package to establish a backup
var ok bool
if conn, ok = sqlite.GetLastConn(); !ok {
return nil, nil, ErrNilSQLite3Conn
}
return db, conn, nil
}