UNPKG

6.88 kBtext/x-cView Raw
1#ifndef NODE_SQLITE3_SRC_BACKUP_H
2#define NODE_SQLITE3_SRC_BACKUP_H
3
4#include "database.h"
5
6#include <string>
7#include <queue>
8#include <set>
9
10#include <sqlite3.h>
11#include <napi.h>
12
13using namespace Napi;
14
15namespace node_sqlite3 {
16
17/**
18 *
19 * A class for managing an sqlite3_backup object. For consistency
20 * with other node-sqlite3 classes, it maintains an internal queue
21 * of calls.
22 *
23 * Intended usage from node:
24 *
25 * var db = new sqlite3.Database('live.db');
26 * var backup = db.backup('backup.db');
27 * ...
28 * // in event loop, move backup forward when we have time.
29 * if (backup.idle) { backup.step(NPAGES); }
30 * if (backup.completed) { ... success ... }
31 * if (backup.failed) { ... sadness ... }
32 * // do other work in event loop - fine to modify live.db
33 * ...
34 *
35 * Here is how sqlite's backup api is exposed:
36 *
37 * - `sqlite3_backup_init`: This is implemented as
38 * `db.backup(filename, [callback])` or
39 * `db.backup(filename, destDbName, sourceDbName, filenameIsDest, [callback])`.
40 * - `sqlite3_backup_step`: `backup.step(pages, [callback])`.
41 * - `sqlite3_backup_finish`: `backup.finish([callback])`.
42 * - `sqlite3_backup_remaining`: `backup.remaining`.
43 * - `sqlite3_backup_pagecount`: `backup.pageCount`.
44 *
45 * There are the following read-only properties:
46 *
47 * - `backup.completed` is set to `true` when the backup
48 * succeeeds.
49 * - `backup.failed` is set to `true` when the backup
50 * has a fatal error.
51 * - `backup.idle` is set to `true` when no operation
52 * is currently in progress or queued for the backup.
53 * - `backup.remaining` is an integer with the remaining
54 * number of pages after the last call to `backup.step`
55 * (-1 if `step` not yet called).
56 * - `backup.pageCount` is an integer with the total number
57 * of pages measured during the last call to `backup.step`
58 * (-1 if `step` not yet called).
59 *
60 * There is the following writable property:
61 *
62 * - `backup.retryErrors`: an array of sqlite3 error codes
63 * that are treated as non-fatal - meaning, if they occur,
64 * backup.failed is not set, and the backup may continue.
65 * By default, this is `[sqlite3.BUSY, sqlite3.LOCKED]`.
66 *
67 * The `db.backup(filename, [callback])` shorthand is sufficient
68 * for making a backup of a database opened by node-sqlite3. If
69 * using attached or temporary databases, or moving data in the
70 * opposite direction, the more complete (but daunting)
71 * `db.backup(filename, destDbName, sourceDbName, filenameIsDest, [callback])`
72 * signature is provided.
73 *
74 * A backup will finish automatically when it succeeds or a fatal
75 * error occurs, meaning it is not necessary to call `db.finish()`.
76 * By default, SQLITE_LOCKED and SQLITE_BUSY errors are not
77 * treated as failures, and the backup will continue if they
78 * occur. The set of errors that are tolerated can be controlled
79 * by setting `backup.retryErrors`. To disable automatic
80 * finishing and stick strictly to sqlite's raw api, set
81 * `backup.retryErrors` to `[]`. In that case, it is necessary
82 * to call `backup.finish()`.
83 *
84 * In the same way as node-sqlite3 databases and statements,
85 * backup methods can be called safely without callbacks, due
86 * to an internal call queue. So for example this naive code
87 * will correctly back up a db, if there are no errors:
88 *
89 * var backup = db.backup('backup.db');
90 * backup.step(-1);
91 * backup.finish();
92 *
93 */
94class Backup : public Napi::ObjectWrap<Backup> {
95public:
96 static Napi::Object Init(Napi::Env env, Napi::Object exports);
97
98 struct Baton {
99 napi_async_work request = NULL;
100 Backup* backup;
101 Napi::FunctionReference callback;
102
103 Baton(Backup* backup_, Napi::Function cb_) : backup(backup_) {
104 backup->Ref();
105 callback.Reset(cb_, 1);
106 }
107 virtual ~Baton() {
108 if (request) napi_delete_async_work(backup->Env(), request);
109 backup->Unref();
110 callback.Reset();
111 }
112 };
113
114 struct InitializeBaton : Database::Baton {
115 Backup* backup;
116 std::string filename;
117 std::string sourceName;
118 std::string destName;
119 bool filenameIsDest;
120 InitializeBaton(Database* db_, Napi::Function cb_, Backup* backup_) :
121 Baton(db_, cb_), backup(backup_), filenameIsDest(true) {
122 backup->Ref();
123 }
124 virtual ~InitializeBaton() override {
125 backup->Unref();
126 if (!db->IsOpen() && db->IsLocked()) {
127 // The database handle was closed before the backup could be opened.
128 backup->FinishAll();
129 }
130 }
131 };
132
133 struct StepBaton : Baton {
134 int pages;
135 std::set<int> retryErrorsSet;
136 StepBaton(Backup* backup_, Napi::Function cb_, int pages_) :
137 Baton(backup_, cb_), pages(pages_) {}
138 virtual ~StepBaton() override = default;
139 };
140
141 typedef void (*Work_Callback)(Baton* baton);
142
143 struct Call {
144 Call(Work_Callback cb_, Baton* baton_) : callback(cb_), baton(baton_) {};
145 Work_Callback callback;
146 Baton* baton;
147 };
148
149 Backup(const Napi::CallbackInfo& info);
150
151 ~Backup() {
152 if (!finished) {
153 FinishAll();
154 }
155 retryErrors.Reset();
156 }
157
158 WORK_DEFINITION(Step)
159 WORK_DEFINITION(Finish)
160
161 Napi::Value IdleGetter(const Napi::CallbackInfo& info);
162 Napi::Value CompletedGetter(const Napi::CallbackInfo& info);
163 Napi::Value FailedGetter(const Napi::CallbackInfo& info);
164 Napi::Value PageCountGetter(const Napi::CallbackInfo& info);
165 Napi::Value RemainingGetter(const Napi::CallbackInfo& info);
166 Napi::Value FatalErrorGetter(const Napi::CallbackInfo& info);
167 Napi::Value RetryErrorGetter(const Napi::CallbackInfo& info);
168
169 void FatalErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value);
170 void RetryErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value);
171
172protected:
173 static void Work_BeginInitialize(Database::Baton* baton);
174 static void Work_Initialize(napi_env env, void* data);
175 static void Work_AfterInitialize(napi_env env, napi_status status, void* data);
176
177 void Schedule(Work_Callback callback, Baton* baton);
178 void Process();
179 void CleanQueue();
180 template <class T> static void Error(T* baton);
181
182 void FinishAll();
183 void FinishSqlite();
184 void GetRetryErrors(std::set<int>& retryErrorsSet);
185
186 Database* db;
187
188 sqlite3_backup* _handle = NULL;
189 sqlite3* _otherDb = NULL;
190 sqlite3* _destDb = NULL;
191
192 bool inited = false;
193 bool locked = true;
194 bool completed = false;
195 bool failed = false;
196 int remaining = -1;
197 int pageCount = -1;
198 bool finished = false;
199
200 int status;
201 std::string message;
202 std::queue<std::unique_ptr<Call>> queue;
203
204 Napi::Reference<Array> retryErrors;
205};
206
207}
208
209#endif