This repository was archived by the owner on Mar 4, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathlive.go
355 lines (302 loc) · 11.5 KB
/
live.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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
package common
import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/sqlitebrowser/dbhub.io/common/config"
"github.com/sqlitebrowser/dbhub.io/common/database"
sqlite "github.com/gwenn/gosqlite"
)
const (
contextTimeout = 5 * time.Second
)
var (
// JobQueueDebug tells the daemons whether or not to output debug messages while running job queue code
// Mostly useful for development / debugging purposes. 0 means no debug messages, higher values means more verbosity
JobQueueDebug = 0
)
// LiveBackup asks the job queue backend to store the given database back into Minio
func LiveBackup(liveNode, loggedInUser, dbOwner, dbName string) (err error) {
// Send the backup request to our job queue backend
var resp JobResponseDBError
err = JobSubmit(&resp, liveNode, "backup", loggedInUser, dbOwner, dbName, "")
if err != nil {
return
}
log.Printf("%s: node which handled the database backup request: %s", config.Conf.Live.Nodename, liveNode)
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
log.Printf("%s: an error was returned during database backup on '%s': '%v'", config.Conf.Live.Nodename, liveNode, resp.Err)
}
return
}
// LiveColumns requests the job queue backend to return a list of all columns of the given table
func LiveColumns(liveNode, loggedInUser, dbOwner, dbName, table string) (columns []sqlite.Column, pk []string, err error) {
// Send the column list request to our job queue backend
var resp JobResponseDBColumns
err = JobSubmit(&resp, liveNode, "columns", loggedInUser, dbOwner, dbName, table)
if err != nil {
return
}
// Return the requested data
columns = resp.Columns
pk = resp.PkColumns
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
log.Printf("%s: an error was returned when retrieving the column list for '%s/%s': '%v'", config.Conf.Live.Nodename, dbOwner, dbName, resp.Err)
}
return
}
// LiveCreateDB requests the job queue backend create a new live SQLite database
func LiveCreateDB(dbOwner, dbName, objectID string) (liveNode string, err error) {
// Send the database setup request to our job queue backend
var resp JobResponseDBCreate
err = JobSubmit(&resp, "any", "createdb", "", dbOwner, dbName, objectID)
if err != nil {
return
}
// Return the name of the node which has the database
liveNode = resp.NodeName
log.Printf("%s: node which handled the database creation request: %s", config.Conf.Live.Nodename, liveNode)
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
log.Printf("%s: an error was returned during database creation on '%s': '%v'", config.Conf.Live.Nodename, resp.NodeName, resp.Err)
}
return
}
// LiveDelete asks our job queue backend to delete a database
func LiveDelete(liveNode, loggedInUser, dbOwner, dbName string) (err error) {
// Send the database setup request to our job queue backend
var resp JobResponseDBError
err = JobSubmit(&resp, liveNode, "delete", loggedInUser, dbOwner, dbName, "")
if err != nil {
return
}
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
log.Printf("%s: an error was returned during database deletion on '%s': '%v'", config.Conf.Live.Nodename, liveNode, resp.Err)
}
return
}
// LiveExecute asks our job queue backend to execute a SQL statement on a database
func LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql string) (rowsChanged int, err error) {
// Send the execute request to our job queue backend
var resp JobResponseDBExecute
err = JobSubmit(&resp, liveNode, "execute", loggedInUser, dbOwner, dbName, sql)
if err != nil {
return
}
// Return the number of rows changed by the execution run
rowsChanged = resp.RowsChanged
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
if !strings.HasPrefix(err.Error(), "don't use exec with") {
log.Printf("%s: an error was returned when retrieving the execution result for '%s/%s': '%v'", config.Conf.Live.Nodename, dbOwner, dbName, resp.Err)
}
}
// If no error was thrown, then update the "last_modified" field for the database
if err == nil {
err = database.UpdateModified(dbOwner, dbName)
}
return
}
// LiveIndexes asks our job queue backend to provide the list of indexes in a database
func LiveIndexes(liveNode, loggedInUser, dbOwner, dbName string) (indexes []APIJSONIndex, err error) {
// Send the index request to our job queue backend
var resp JobResponseDBIndexes
err = JobSubmit(&resp, liveNode, "indexes", loggedInUser, dbOwner, dbName, "")
if err != nil {
return
}
// Return the index list for the live database
indexes = resp.Indexes
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
log.Printf("%s: an error was returned when retrieving the index list for '%s/%s': '%v'", config.Conf.Live.Nodename, dbOwner, dbName, resp.Err)
}
return
}
// LiveQuery sends a SQLite query to a live database on its hosting node
func LiveQuery(liveNode, loggedInUser, dbOwner, dbName, query string) (rows SQLiteRecordSet, err error) {
// Send the query to our job queue backend
var resp JobResponseDBQuery
err = JobSubmit(&resp, liveNode, "query", loggedInUser, dbOwner, dbName, query)
if err != nil {
return
}
// Return the query response
rows = resp.Results
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
log.Printf("%s: an error was returned when retrieving the query response for '%s/%s': '%v'", config.Conf.Live.Nodename, dbOwner, dbName, resp.Err)
}
return
}
// LiveRowData asks our job queue backend to send us the SQLite table data for a given range of rows
func LiveRowData(liveNode, loggedInUser, dbOwner, dbName string, reqData JobRequestRows) (rowData SQLiteRecordSet, err error) {
// Serialise the row data request to JSON
// NOTE - This actually causes the serialised field to be stored in PG as base64 instead. Not sure why, but we can work with it.
var reqJSON []byte
reqJSON, err = json.Marshal(reqData)
if err != nil {
log.Println(err)
return
}
// Send the row data request to our job queue backend
var resp JobResponseDBRows
err = JobSubmit(&resp, liveNode, "rowdata", loggedInUser, dbOwner, dbName, reqJSON)
if err != nil {
return
}
// Return the row data for the requested table
rowData = resp.RowData
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
log.Printf("%s: an error was returned when retrieving the row data for '%s/%s': '%v'", config.Conf.Live.Nodename, dbOwner, dbName, resp.Err)
}
return
}
// LiveSize asks our job queue backend for the file size of a database
func LiveSize(liveNode, loggedInUser, dbOwner, dbName string) (size int64, err error) {
// Send the size request to our job queue backend
var resp JobResponseDBSize
err = JobSubmit(&resp, liveNode, "size", loggedInUser, dbOwner, dbName, "")
if err != nil {
return
}
// Return the size of the live database
size = resp.Size
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
log.Printf("%s: an error was returned when checking the on disk database size for '%s/%s': '%v'", config.Conf.Live.Nodename, dbOwner, dbName, resp.Err)
}
return
}
// LiveTables asks our job queue backend to provide the list of tables (not including views!) in a database
func LiveTables(liveNode, loggedInUser, dbOwner, dbName string) (tables []string, err error) {
// Send the tables request to our job queue backend
var resp JobResponseDBTables
err = JobSubmit(&resp, liveNode, "tables", loggedInUser, dbOwner, dbName, "")
if err != nil {
return
}
// Return the table list for the live database
tables = resp.Tables
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
log.Printf("%s: an error was returned when retrieving the table list for '%s/%s': '%v'", config.Conf.Live.Nodename, dbOwner, dbName, resp.Err)
}
return
}
// LiveTablesAndViews asks our job queue backend to provide the list of tables and views in a database
func LiveTablesAndViews(liveNode, loggedInUser, dbOwner, dbName string) (list []string, err error) {
// Send the tables request to our job queue backend
list, err = LiveTables(liveNode, loggedInUser, dbOwner, dbName)
if err != nil {
return
}
// Send the tables request to our job queue backend
var vw []string
vw, err = LiveViews(liveNode, loggedInUser, dbOwner, dbName)
if err != nil {
return
}
// Merge the table and view lists
list = append(list, vw...)
sort.Strings(list)
return
}
// LiveViews asks our job queue backend to provide the list of views (not including tables!) in a database
func LiveViews(liveNode, loggedInUser, dbOwner, dbName string) (views []string, err error) {
// Send the views request to our job queue backend
var resp JobResponseDBViews
err = JobSubmit(&resp, liveNode, "views", loggedInUser, dbOwner, dbName, "")
if err != nil {
return
}
// Return the view list for the live database
views = resp.Views
// Handle error response from the live node
if resp.Err != "" {
err = errors.New(resp.Err)
log.Printf("%s: an error was returned when retrieving the view list for '%s/%s': '%v'", config.Conf.Live.Nodename, dbOwner, dbName, resp.Err)
}
return
}
// RemoveLiveDB deletes a live database from the local node. For example, when the user deletes it from
// their account.
// Be aware, it leaves the database owners directory in place, to avoid any potential race condition of
// trying to delete that directory while other databases in their account are being worked with
func RemoveLiveDB(dbOwner, dbName string) (err error) {
// Get the path to the database file, and it's containing directory
dbDir := filepath.Join(config.Conf.Live.StorageDir, dbOwner, dbName)
dbPath := filepath.Join(dbDir, "live.sqlite")
if _, err = os.Stat(dbPath); err != nil {
if errors.Is(err, fs.ErrNotExist) {
if JobQueueDebug > 0 {
log.Printf("%s: database file '%s/%s' was supposed to get deleted here, but was missing from "+
"filesystem path: '%s'", config.Conf.Live.Nodename, dbOwner, dbName, dbPath)
}
return
}
// Something wrong with the database file
log.Println(err)
return
}
// Delete the "live.sqlite" file
// NOTE: If this seems to leave wal or other files hanging around in actual production use, we could
// instead use filepath.RemoveAll(dbDir). That should kill the containing directory and
// all files within, thus not leave anything hanging around
err = os.Remove(dbPath)
if err != nil {
log.Println(err)
return
}
// Remove the containing directory
err = os.Remove(dbDir)
if err != nil {
log.Println(err)
return
}
if JobQueueDebug > 0 {
log.Printf("%s: database file '%s/%s' removed from filesystem path: '%s'", config.Conf.Live.Nodename, dbOwner,
dbName, dbPath)
}
return
}
// WaitForResponse waits for the job queue server to provide a response for a given job id
func WaitForResponse[T any](jobID int, resp *T) (err error) {
// Add the response receiver
responseChan := make(chan ResponseInfo)
ResponseQueue.AddReceiver(jobID, &responseChan)
// Wait for a response
response := <-responseChan
// Remove the response receiver
ResponseQueue.RemoveReceiver(jobID)
// Update the response status to 'processed' (should be fine done async)
go ResponseComplete(response.responseID)
// Unmarshall the response
err = json.Unmarshal([]byte(response.payload), resp)
if err != nil {
err = fmt.Errorf("couldn't decode response payload: '%s'", err)
log.Printf("%s: %s", config.Conf.Live.Nodename, err)
}
return
}