-
Notifications
You must be signed in to change notification settings - Fork 67
/
snapshot.go
192 lines (178 loc) · 6.87 KB
/
snapshot.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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// Copyright (c) 2018 David Crawshaw <[email protected]>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package sqlite
// #include <sqlite3.h>
// #include <stdlib.h>
import "C"
import (
"runtime"
"unsafe"
)
// A Snapshot records the state of a WAL mode database for some specific point
// in history.
//
// Equivalent to the sqlite3_snapshot* C object.
//
// https://www.sqlite.org/c3ref/snapshot.html
type Snapshot struct {
ptr *C.sqlite3_snapshot
schema *C.char
}
// GetSnapshot attempts to make a new Snapshot that records the current state
// of the given schema in conn. If successful, a *Snapshot and a func() is
// returned, and the conn will have an open READ transaction which will
// continue to reflect the state of the Snapshot until the returned func() is
// called. No WRITE transaction may occur on conn until the returned func() is
// called.
//
// The returned *Snapshot is threadsafe for creating additional read
// transactions that reflect its state with Conn.StartSnapshotRead.
//
// In theory, so long as at least one read transaction is open on the Snapshot,
// then the WAL file will not be checkpointed past that point, and the Snapshot
// will continue to be available for creating additional read transactions.
// However, if no read transaction is open on the Snapshot, then it is possible
// for the WAL to be checkpointed past the point of the Snapshot. If this
// occurs then there is no way to start a read on the Snapshot. In order to
// ensure that a Snapshot remains readable, always maintain at least one open
// read transaction on the Snapshot.
//
// In practice, this is generally reliable but sometimes the Snapshot can
// sometimes become unavailable for reads unless automatic checkpointing is
// entirely disabled from the start.
//
// The returned *Snapshot has a finalizer that calls Free if it has not been
// called, so it is safe to allow a Snapshot to be garbage collected. However,
// if you are sure that a Snapshot will never be used again by any thread, you
// may call Free once to release the memory earlier. No reads will be possible
// on the Snapshot after Free is called on it, however any open read
// transactions will not be interrupted.
//
// See sqlitex.Pool.GetSnapshot for a helper function for automatically keeping
// an open read transaction on a set aside connection until a Snapshot is GC'd.
//
// The following must be true for this function to succeed:
//
// - The schema of conn must be a WAL mode database.
//
// - There must not be any transaction open on schema of conn.
//
// - At least one transaction must have been written to the current WAL file
// since it was created on disk (by any connection). You can run the following
// SQL to ensure that a WAL file has been created.
// BEGIN IMMEDIATE;
// COMMIT;
//
// https://www.sqlite.org/c3ref/snapshot_get.html
func (conn *Conn) GetSnapshot(schema string) (*Snapshot, func(), error) {
var s Snapshot
if schema == "" || schema == "main" {
s.schema = cmain
} else {
s.schema = C.CString(schema)
}
endRead, err := conn.disableAutoCommitMode()
if err != nil {
return nil, nil, err
}
res := C.sqlite3_snapshot_get(conn.conn, s.schema, &s.ptr)
if res != 0 {
endRead()
return nil, nil, reserr("Conn.CreateSnapshot", "", "", res)
}
runtime.SetFinalizer(&s, func(s *Snapshot) {
s.Free()
})
return &s, endRead, nil
}
// Free destroys a Snapshot. Free is not threadsafe but may be called more than
// once. However, it is not necessary to call Free on a Snapshot returned by
// conn.GetSnapshot or pool.GetSnapshot as these set a finalizer that calls
// free which will be run automatically by the GC in a finalizer. However if it
// is guaranteed that a Snapshot will never be used again, calling Free will
// allow memory to be freed earlier.
//
// A Snapshot may become unavailable for reads before Free is called if the WAL
// is checkpointed into the DB past the point of the Snapshot.
//
// https://www.sqlite.org/c3ref/snapshot_free.html
func (s *Snapshot) Free() {
if s.ptr == nil {
return
}
C.sqlite3_snapshot_free(s.ptr)
if s.schema != cmain {
C.free(unsafe.Pointer(s.schema))
}
s.ptr = nil
}
// CompareAges returns whether s1 is older, newer or the same age as s2. Age
// refers to writes on the database, not time since creation.
//
// If s is older than s2, a negative number is returned. If s and s2 are the
// same age, zero is returned. If s is newer than s2, a positive number is
// returned.
//
// The result is valid only if both of the following are true:
//
// - The two snapshot handles are associated with the same database file.
//
// - Both of the Snapshots were obtained since the last time the wal file was
// deleted.
//
// https://www.sqlite.org/c3ref/snapshot_cmp.html
func (s *Snapshot) CompareAges(s2 *Snapshot) int {
return int(C.sqlite3_snapshot_cmp(s.ptr, s2.ptr))
}
// StartSnapshotRead starts a new read transaction on conn such that the read
// transaction refers to historical Snapshot s, rather than the most recent
// change to the database.
//
// There must be no open transaction on conn. Free must not have been called on
// s prior to or during this function call.
//
// If err is nil, then endRead is a function that will end the read transaction
// and return conn to its original state. Until endRead is called, no writes
// may occur on conn, and all reads on conn will refer to the Snapshot.
//
// https://www.sqlite.org/c3ref/snapshot_open.html
func (conn *Conn) StartSnapshotRead(s *Snapshot) (endRead func(), err error) {
endRead, err = conn.disableAutoCommitMode()
if err != nil {
return
}
res := C.sqlite3_snapshot_open(conn.conn, s.schema, s.ptr)
if res != 0 {
endRead()
return nil, reserr("Conn.StartSnapshotRead", "", "", res)
}
return endRead, nil
}
// disableAutoCommitMode starts a read transaction with `BEGIN;`, disabling
// autocommit mode, and returns a function which when called will end the read
// transaction with `ROLLBACK;`, re-enabling autocommit mode.
//
// https://sqlite.org/c3ref/get_autocommit.html
func (conn *Conn) disableAutoCommitMode() (func(), error) {
begin := conn.Prep("BEGIN;")
defer begin.Reset()
if _, err := begin.Step(); err != nil {
return nil, err
}
rollback := conn.Prep("ROLLBACK;")
return func() {
defer rollback.Reset()
rollback.Step()
}, nil
}