8
8
#include " node_errors.h"
9
9
#include " node_mem-inl.h"
10
10
#include " sqlite3.h"
11
+ #include " threadpoolwork-inl.h"
11
12
#include " util-inl.h"
12
13
13
14
#include < cinttypes>
@@ -29,6 +30,7 @@ using v8::FunctionCallback;
29
30
using v8::FunctionCallbackInfo;
30
31
using v8::FunctionTemplate;
31
32
using v8::Global;
33
+ using v8::HandleScope;
32
34
using v8::Int32;
33
35
using v8::Integer;
34
36
using v8::Isolate;
@@ -40,6 +42,7 @@ using v8::NewStringType;
40
42
using v8::Null;
41
43
using v8::Number;
42
44
using v8::Object;
45
+ using v8::Promise;
43
46
using v8::SideEffectType;
44
47
using v8::String;
45
48
using v8::TryCatch;
@@ -81,6 +84,23 @@ inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate,
81
84
return e;
82
85
}
83
86
87
+ inline MaybeLocal<Object> CreateSQLiteError (Isolate* isolate, int errcode) {
88
+ const char * errstr = sqlite3_errstr (errcode);
89
+ Local<String> js_errmsg;
90
+ Local<Object> e;
91
+ Environment* env = Environment::GetCurrent (isolate);
92
+ if (!String::NewFromUtf8 (isolate, errstr).ToLocal (&js_errmsg) ||
93
+ !CreateSQLiteError (isolate, errstr).ToLocal (&e) ||
94
+ e->Set (env->context (),
95
+ env->errcode_string (),
96
+ Integer::New (isolate, errcode))
97
+ .IsNothing () ||
98
+ e->Set (env->context (), env->errstr_string (), js_errmsg).IsNothing ()) {
99
+ return MaybeLocal<Object>();
100
+ }
101
+ return e;
102
+ }
103
+
84
104
inline MaybeLocal<Object> CreateSQLiteError (Isolate* isolate, sqlite3* db) {
85
105
int errcode = sqlite3_extended_errcode (db);
86
106
const char * errstr = sqlite3_errstr (errcode);
@@ -137,6 +157,169 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) {
137
157
}
138
158
}
139
159
160
+ class BackupJob : public ThreadPoolWork {
161
+ public:
162
+ explicit BackupJob (Environment* env,
163
+ DatabaseSync* source,
164
+ Local<Promise::Resolver> resolver,
165
+ std::string source_db,
166
+ std::string destination_name,
167
+ std::string dest_db,
168
+ int pages,
169
+ Local<Function> progressFunc)
170
+ : ThreadPoolWork(env, " node_sqlite3.BackupJob" ),
171
+ env_(env),
172
+ source_(source),
173
+ pages_(pages),
174
+ source_db_(source_db),
175
+ destination_name_(destination_name),
176
+ dest_db_(dest_db) {
177
+ resolver_.Reset (env->isolate (), resolver);
178
+ progressFunc_.Reset (env->isolate (), progressFunc);
179
+ }
180
+
181
+ void ScheduleBackup () {
182
+ Isolate* isolate = env ()->isolate ();
183
+ HandleScope handle_scope (isolate);
184
+ backup_status_ = sqlite3_open_v2 (destination_name_.c_str (),
185
+ &dest_,
186
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
187
+ nullptr );
188
+ Local<Promise::Resolver> resolver =
189
+ Local<Promise::Resolver>::New (env ()->isolate (), resolver_);
190
+ if (backup_status_ != SQLITE_OK) {
191
+ HandleBackupError (resolver);
192
+ return ;
193
+ }
194
+
195
+ backup_ = sqlite3_backup_init (
196
+ dest_, dest_db_.c_str (), source_->Connection (), source_db_.c_str ());
197
+ if (backup_ == nullptr ) {
198
+ HandleBackupError (resolver);
199
+ return ;
200
+ }
201
+
202
+ this ->ScheduleWork ();
203
+ }
204
+
205
+ void DoThreadPoolWork () override {
206
+ backup_status_ = sqlite3_backup_step (backup_, pages_);
207
+ }
208
+
209
+ void AfterThreadPoolWork (int status) override {
210
+ HandleScope handle_scope (env ()->isolate ());
211
+ Local<Promise::Resolver> resolver =
212
+ Local<Promise::Resolver>::New (env ()->isolate (), resolver_);
213
+
214
+ if (!(backup_status_ == SQLITE_OK || backup_status_ == SQLITE_DONE ||
215
+ backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED)) {
216
+ HandleBackupError (resolver, backup_status_);
217
+ return ;
218
+ }
219
+
220
+ int total_pages = sqlite3_backup_pagecount (backup_);
221
+ int remaining_pages = sqlite3_backup_remaining (backup_);
222
+ if (remaining_pages != 0 ) {
223
+ Local<Function> fn =
224
+ Local<Function>::New (env ()->isolate (), progressFunc_);
225
+ if (!fn.IsEmpty ()) {
226
+ Local<Object> progress_info = Object::New (env ()->isolate ());
227
+ if (progress_info
228
+ ->Set (env ()->context (),
229
+ env ()->total_pages_string (),
230
+ Integer::New (env ()->isolate (), total_pages))
231
+ .IsNothing () ||
232
+ progress_info
233
+ ->Set (env ()->context (),
234
+ env ()->remaining_pages_string (),
235
+ Integer::New (env ()->isolate (), remaining_pages))
236
+ .IsNothing ()) {
237
+ return ;
238
+ }
239
+
240
+ Local<Value> argv[] = {progress_info};
241
+ TryCatch try_catch (env ()->isolate ());
242
+ fn->Call (env ()->context (), Null (env ()->isolate ()), 1 , argv)
243
+ .FromMaybe (Local<Value>());
244
+ if (try_catch.HasCaught ()) {
245
+ Finalize ();
246
+ resolver->Reject (env ()->context (), try_catch.Exception ()).ToChecked ();
247
+ return ;
248
+ }
249
+ }
250
+
251
+ // There's still work to do
252
+ this ->ScheduleWork ();
253
+ return ;
254
+ }
255
+
256
+ if (backup_status_ != SQLITE_DONE) {
257
+ HandleBackupError (resolver);
258
+ return ;
259
+ }
260
+
261
+ Finalize ();
262
+ resolver
263
+ ->Resolve (env ()->context (), Integer::New (env ()->isolate (), total_pages))
264
+ .ToChecked ();
265
+ }
266
+
267
+ void Finalize () {
268
+ Cleanup ();
269
+ source_->RemoveBackup (this );
270
+ }
271
+
272
+ void Cleanup () {
273
+ if (backup_) {
274
+ sqlite3_backup_finish (backup_);
275
+ backup_ = nullptr ;
276
+ }
277
+
278
+ if (dest_) {
279
+ backup_status_ = sqlite3_errcode (dest_);
280
+ sqlite3_close_v2 (dest_);
281
+ dest_ = nullptr ;
282
+ }
283
+ }
284
+
285
+ private:
286
+ void HandleBackupError (Local<Promise::Resolver> resolver) {
287
+ Local<Object> e;
288
+ if (!CreateSQLiteError (env ()->isolate (), dest_).ToLocal (&e)) {
289
+ Finalize ();
290
+ return ;
291
+ }
292
+
293
+ Finalize ();
294
+ resolver->Reject (env ()->context (), e).ToChecked ();
295
+ }
296
+
297
+ void HandleBackupError (Local<Promise::Resolver> resolver, int errcode) {
298
+ Local<Object> e;
299
+ if (!CreateSQLiteError (env ()->isolate (), errcode).ToLocal (&e)) {
300
+ Finalize ();
301
+ return ;
302
+ }
303
+
304
+ Finalize ();
305
+ resolver->Reject (env ()->context (), e).ToChecked ();
306
+ }
307
+
308
+ Environment* env () const { return env_; }
309
+
310
+ Environment* env_;
311
+ DatabaseSync* source_;
312
+ Global<Promise::Resolver> resolver_;
313
+ Global<Function> progressFunc_;
314
+ sqlite3* dest_ = nullptr ;
315
+ sqlite3_backup* backup_ = nullptr ;
316
+ int pages_;
317
+ int backup_status_;
318
+ std::string source_db_;
319
+ std::string destination_name_;
320
+ std::string dest_db_;
321
+ };
322
+
140
323
UserDefinedFunction::UserDefinedFunction (Environment* env,
141
324
Local<Function> fn,
142
325
DatabaseSync* db,
@@ -279,6 +462,14 @@ DatabaseSync::DatabaseSync(Environment* env,
279
462
}
280
463
}
281
464
465
+ void DatabaseSync::AddBackup (BackupJob* job) {
466
+ backups_.insert (job);
467
+ }
468
+
469
+ void DatabaseSync::RemoveBackup (BackupJob* job) {
470
+ backups_.erase (job);
471
+ }
472
+
282
473
void DatabaseSync::DeleteSessions () {
283
474
// all attached sessions need to be deleted before the database is closed
284
475
// https://www.sqlite.org/session/sqlite3session_create.html
@@ -289,6 +480,8 @@ void DatabaseSync::DeleteSessions() {
289
480
}
290
481
291
482
DatabaseSync::~DatabaseSync () {
483
+ FinalizeBackups ();
484
+
292
485
if (IsOpen ()) {
293
486
FinalizeStatements ();
294
487
DeleteSessions ();
@@ -353,6 +546,14 @@ bool DatabaseSync::Open() {
353
546
return true ;
354
547
}
355
548
549
+ void DatabaseSync::FinalizeBackups () {
550
+ for (auto backup : backups_) {
551
+ backup->Cleanup ();
552
+ }
553
+
554
+ backups_.clear ();
555
+ }
556
+
356
557
void DatabaseSync::FinalizeStatements () {
357
558
for (auto stmt : statements_) {
358
559
stmt->Finalize ();
@@ -772,6 +973,117 @@ void DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) {
772
973
args.GetReturnValue ().Set (session->object ());
773
974
}
774
975
976
+ void Backup (const FunctionCallbackInfo<Value>& args) {
977
+ Environment* env = Environment::GetCurrent (args);
978
+ if (args.Length () < 1 || !args[0 ]->IsObject ()) {
979
+ THROW_ERR_INVALID_ARG_TYPE (env->isolate (),
980
+ " The \" sourceDb\" argument must be an object." );
981
+ return ;
982
+ }
983
+
984
+ DatabaseSync* db;
985
+ ASSIGN_OR_RETURN_UNWRAP (&db, args[0 ].As <Object>());
986
+ THROW_AND_RETURN_ON_BAD_STATE (env, !db->IsOpen (), " database is not open" );
987
+ if (!args[1 ]->IsString ()) {
988
+ THROW_ERR_INVALID_ARG_TYPE (
989
+ env->isolate (), " The \" destination\" argument must be a string." );
990
+ return ;
991
+ }
992
+
993
+ int rate = 100 ;
994
+ std::string source_db = " main" ;
995
+ std::string dest_db = " main" ;
996
+
997
+ Utf8Value dest_path (env->isolate (), args[1 ].As <String>());
998
+ Local<Function> progressFunc = Local<Function>();
999
+
1000
+ if (args.Length () > 2 ) {
1001
+ if (!args[2 ]->IsObject ()) {
1002
+ THROW_ERR_INVALID_ARG_TYPE (env->isolate (),
1003
+ " The \" options\" argument must be an object." );
1004
+ return ;
1005
+ }
1006
+
1007
+ Local<Object> options = args[2 ].As <Object>();
1008
+ Local<Value> rate_v;
1009
+ if (!options->Get (env->context (), env->rate_string ()).ToLocal (&rate_v)) {
1010
+ return ;
1011
+ }
1012
+
1013
+ if (!rate_v->IsUndefined ()) {
1014
+ if (!rate_v->IsInt32 ()) {
1015
+ THROW_ERR_INVALID_ARG_TYPE (
1016
+ env->isolate (),
1017
+ " The \" options.rate\" argument must be an integer." );
1018
+ return ;
1019
+ }
1020
+ rate = rate_v.As <Int32>()->Value ();
1021
+ }
1022
+
1023
+ Local<Value> source_v;
1024
+ if (!options->Get (env->context (), env->source_string ())
1025
+ .ToLocal (&source_v)) {
1026
+ return ;
1027
+ }
1028
+
1029
+ if (!source_v->IsUndefined ()) {
1030
+ if (!source_v->IsString ()) {
1031
+ THROW_ERR_INVALID_ARG_TYPE (
1032
+ env->isolate (),
1033
+ " The \" options.source\" argument must be a string." );
1034
+ return ;
1035
+ }
1036
+
1037
+ source_db = Utf8Value (env->isolate (), source_v.As <String>()).ToString ();
1038
+ }
1039
+
1040
+ Local<Value> target_v;
1041
+ if (!options->Get (env->context (), env->target_string ())
1042
+ .ToLocal (&target_v)) {
1043
+ return ;
1044
+ }
1045
+
1046
+ if (!target_v->IsUndefined ()) {
1047
+ if (!target_v->IsString ()) {
1048
+ THROW_ERR_INVALID_ARG_TYPE (
1049
+ env->isolate (),
1050
+ " The \" options.target\" argument must be a string." );
1051
+ return ;
1052
+ }
1053
+
1054
+ dest_db = Utf8Value (env->isolate (), target_v.As <String>()).ToString ();
1055
+ }
1056
+
1057
+ Local<Value> progress_v;
1058
+ if (!options->Get (env->context (), env->progress_string ())
1059
+ .ToLocal (&progress_v)) {
1060
+ return ;
1061
+ }
1062
+
1063
+ if (!progress_v->IsUndefined ()) {
1064
+ if (!progress_v->IsFunction ()) {
1065
+ THROW_ERR_INVALID_ARG_TYPE (
1066
+ env->isolate (),
1067
+ " The \" options.progress\" argument must be a function." );
1068
+ return ;
1069
+ }
1070
+ progressFunc = progress_v.As <Function>();
1071
+ }
1072
+ }
1073
+
1074
+ Local<Promise::Resolver> resolver;
1075
+ if (!Promise::Resolver::New (env->context ()).ToLocal (&resolver)) {
1076
+ return ;
1077
+ }
1078
+
1079
+ args.GetReturnValue ().Set (resolver->GetPromise ());
1080
+
1081
+ BackupJob* job = new BackupJob (
1082
+ env, db, resolver, source_db, *dest_path, dest_db, rate, progressFunc);
1083
+ db->AddBackup (job);
1084
+ job->ScheduleBackup ();
1085
+ }
1086
+
775
1087
// the reason for using static functions here is that SQLite needs a
776
1088
// function pointer
777
1089
static std::function<int (int )> conflictCallback;
@@ -1803,6 +2115,14 @@ static void Initialize(Local<Object> target,
1803
2115
StatementSync::GetConstructorTemplate (env));
1804
2116
1805
2117
target->Set (context, env->constants_string (), constants).Check ();
2118
+
2119
+ Local<Function> backup_function;
2120
+
2121
+ if (!Function::New (context, Backup).ToLocal (&backup_function)) {
2122
+ return ;
2123
+ }
2124
+
2125
+ target->Set (context, env->backup_string (), backup_function).Check ();
1806
2126
}
1807
2127
1808
2128
} // namespace sqlite
0 commit comments