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,24 @@ 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 (isolate->GetCurrentContext (),
95
+ env->errcode_string (),
96
+ Integer::New (isolate, errcode))
97
+ .IsNothing () ||
98
+ e->Set (isolate->GetCurrentContext (), env->errstr_string (), js_errmsg)
99
+ .IsNothing ()) {
100
+ return MaybeLocal<Object>();
101
+ }
102
+ return e;
103
+ }
104
+
84
105
inline MaybeLocal<Object> CreateSQLiteError (Isolate* isolate, sqlite3* db) {
85
106
int errcode = sqlite3_extended_errcode (db);
86
107
const char * errstr = sqlite3_errstr (errcode);
@@ -128,6 +149,171 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) {
128
149
isolate->ThrowException (error);
129
150
}
130
151
152
+ class BackupJob : public ThreadPoolWork {
153
+ public:
154
+ explicit BackupJob (Environment* env,
155
+ DatabaseSync* source,
156
+ Local<Promise::Resolver> resolver,
157
+ std::string source_db,
158
+ std::string destination_name,
159
+ std::string dest_db,
160
+ int pages,
161
+ Local<Function> progressFunc)
162
+ : ThreadPoolWork(env, " node_sqlite3.BackupJob" ),
163
+ env_(env),
164
+ source_(source),
165
+ source_db_(source_db),
166
+ destination_name_(destination_name),
167
+ dest_db_(dest_db),
168
+ pages_(pages) {
169
+ resolver_.Reset (env->isolate (), resolver);
170
+ progressFunc_.Reset (env->isolate (), progressFunc);
171
+ }
172
+
173
+ void ScheduleBackup () {
174
+ Isolate* isolate = env ()->isolate ();
175
+ HandleScope handle_scope (isolate);
176
+
177
+ backup_status_ = sqlite3_open (destination_name_.c_str (), &pDest_);
178
+
179
+ Local<Promise::Resolver> resolver =
180
+ Local<Promise::Resolver>::New (env ()->isolate (), resolver_);
181
+
182
+ Local<Object> e = Local<Object>();
183
+
184
+ if (backup_status_ != SQLITE_OK) {
185
+ CreateSQLiteError (isolate, pDest_).ToLocal (&e);
186
+
187
+ Cleanup ();
188
+
189
+ resolver->Reject (env ()->context (), e).ToChecked ();
190
+
191
+ return ;
192
+ }
193
+
194
+ pBackup_ = sqlite3_backup_init (
195
+ pDest_, dest_db_.c_str (), source_->Connection (), source_db_.c_str ());
196
+
197
+ if (pBackup_ == nullptr ) {
198
+ CreateSQLiteError (isolate, pDest_).ToLocal (&e);
199
+
200
+ sqlite3_close (pDest_);
201
+
202
+ resolver->Reject (env ()->context (), e).ToChecked ();
203
+
204
+ return ;
205
+ }
206
+
207
+ this ->ScheduleWork ();
208
+ }
209
+
210
+ void DoThreadPoolWork () override {
211
+ backup_status_ = sqlite3_backup_step (pBackup_, pages_);
212
+
213
+ const char * errstr = sqlite3_errstr (backup_status_);
214
+ }
215
+
216
+ void AfterThreadPoolWork (int status) override {
217
+ HandleScope handle_scope (env ()->isolate ());
218
+
219
+ if (resolver_.IsEmpty ()) {
220
+ Cleanup ();
221
+
222
+ return ;
223
+ }
224
+
225
+ Local<Promise::Resolver> resolver =
226
+ Local<Promise::Resolver>::New (env ()->isolate (), resolver_);
227
+
228
+ if (!(backup_status_ == SQLITE_OK || backup_status_ == SQLITE_DONE ||
229
+ backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED)) {
230
+ Local<Object> e = Local<Object>();
231
+
232
+ CreateSQLiteError (env ()->isolate (), backup_status_).ToLocal (&e);
233
+
234
+ Cleanup ();
235
+
236
+ resolver->Reject (env ()->context (), e).ToChecked ();
237
+
238
+ return ;
239
+ }
240
+
241
+ int total_pages = sqlite3_backup_pagecount (pBackup_);
242
+ int remaining_pages = sqlite3_backup_remaining (pBackup_);
243
+
244
+ if (remaining_pages != 0 ) {
245
+ Local<Function> fn =
246
+ Local<Function>::New (env ()->isolate (), progressFunc_);
247
+
248
+ if (!fn.IsEmpty ()) {
249
+ Local<Value> argv[] = {
250
+ Integer::New (env ()->isolate (), total_pages),
251
+ Integer::New (env ()->isolate (), remaining_pages),
252
+ };
253
+
254
+ TryCatch try_catch (env ()->isolate ());
255
+ fn->Call (env ()->context (), Null (env ()->isolate ()), 2 , argv)
256
+ .FromMaybe (Local<Value>());
257
+
258
+ if (try_catch.HasCaught ()) {
259
+ Cleanup ();
260
+
261
+ resolver->Reject (env ()->context (), try_catch.Exception ()).ToChecked ();
262
+
263
+ return ;
264
+ }
265
+ }
266
+
267
+ // There's still work to do
268
+ this ->ScheduleWork ();
269
+
270
+ return ;
271
+ }
272
+
273
+ Local<String> message =
274
+ String::NewFromUtf8 (
275
+ env ()->isolate (), " Backup completed" , NewStringType::kNormal )
276
+ .ToLocalChecked ();
277
+
278
+ Local<Object> e = Local<Object>();
279
+ CreateSQLiteError (env ()->isolate (), pDest_).ToLocal (&e);
280
+
281
+ Cleanup ();
282
+
283
+ if (backup_status_ == SQLITE_OK) {
284
+ resolver->Resolve (env ()->context (), message).ToChecked ();
285
+ } else {
286
+ resolver->Reject (env ()->context (), e).ToChecked ();
287
+ }
288
+ }
289
+
290
+ private:
291
+ void Cleanup () {
292
+ if (pBackup_) {
293
+ sqlite3_backup_finish (pBackup_);
294
+ }
295
+
296
+ if (pDest_) {
297
+ backup_status_ = sqlite3_errcode (pDest_);
298
+ sqlite3_close (pDest_);
299
+ }
300
+ }
301
+
302
+ // https://github.com/nodejs/node/blob/649da3b8377e030ea7b9a1bc0308451e26e28740/src/crypto/crypto_keygen.h#L126
303
+ int backup_status_;
304
+ Environment* env () const { return env_; }
305
+ sqlite3* pDest_;
306
+ sqlite3_backup* pBackup_;
307
+ Environment* env_;
308
+ DatabaseSync* source_;
309
+ Global<Promise::Resolver> resolver_;
310
+ Global<Function> progressFunc_;
311
+ std::string source_db_;
312
+ std::string destination_name_;
313
+ std::string dest_db_;
314
+ int pages_;
315
+ };
316
+
131
317
class UserDefinedFunction {
132
318
public:
133
319
explicit UserDefinedFunction (Environment* env,
@@ -533,6 +719,115 @@ void DatabaseSync::Exec(const FunctionCallbackInfo<Value>& args) {
533
719
CHECK_ERROR_OR_THROW (env->isolate (), db->connection_ , r, SQLITE_OK, void ());
534
720
}
535
721
722
+ // database.backup(destination, { sourceDb, targetDb, rate, progress: (total,
723
+ // remaining) => {} )
724
+ void DatabaseSync::Backup (const FunctionCallbackInfo<Value>& args) {
725
+ Environment* env = Environment::GetCurrent (args);
726
+
727
+ if (!args[0 ]->IsString ()) {
728
+ THROW_ERR_INVALID_ARG_TYPE (
729
+ env->isolate (), " The \" destination\" argument must be a string." );
730
+ return ;
731
+ }
732
+
733
+ int rate = 100 ;
734
+ std::string source_db = " main" ;
735
+ std::string dest_db = " main" ;
736
+
737
+ DatabaseSync* db;
738
+ ASSIGN_OR_RETURN_UNWRAP (&db, args.This ());
739
+
740
+ THROW_AND_RETURN_ON_BAD_STATE (env, !db->IsOpen (), " database is not open" );
741
+
742
+ Utf8Value destFilename (env->isolate (), args[0 ].As <String>());
743
+ Local<Function> progressFunc = Local<Function>();
744
+
745
+ if (args.Length () > 1 ) {
746
+ if (!args[1 ]->IsObject ()) {
747
+ THROW_ERR_INVALID_ARG_TYPE (env->isolate (),
748
+ " The \" options\" argument must be an object." );
749
+ return ;
750
+ }
751
+
752
+ Local<Object> options = args[1 ].As <Object>();
753
+ Local<String> progress_string =
754
+ FIXED_ONE_BYTE_STRING (env->isolate (), " progress" );
755
+ Local<String> rate_string = FIXED_ONE_BYTE_STRING (env->isolate (), " rate" );
756
+ Local<String> target_db_string =
757
+ FIXED_ONE_BYTE_STRING (env->isolate (), " targetDb" );
758
+ Local<String> source_db_string =
759
+ FIXED_ONE_BYTE_STRING (env->isolate (), " sourceDb" );
760
+
761
+ Local<Value> rateValue =
762
+ options->Get (env->context (), rate_string).ToLocalChecked ();
763
+
764
+ if (!rateValue->IsUndefined ()) {
765
+ if (!rateValue->IsInt32 ()) {
766
+ THROW_ERR_INVALID_ARG_TYPE (
767
+ env->isolate (),
768
+ " The \" options.rate\" argument must be an integer." );
769
+ return ;
770
+ }
771
+
772
+ rate = rateValue.As <Int32>()->Value ();
773
+ }
774
+
775
+ Local<Value> sourceDbValue =
776
+ options->Get (env->context (), source_db_string).ToLocalChecked ();
777
+
778
+ if (!sourceDbValue->IsUndefined ()) {
779
+ if (!sourceDbValue->IsString ()) {
780
+ THROW_ERR_INVALID_ARG_TYPE (
781
+ env->isolate (),
782
+ " The \" options.sourceDb\" argument must be a string." );
783
+ return ;
784
+ }
785
+
786
+ source_db =
787
+ Utf8Value (env->isolate (), sourceDbValue.As <String>()).ToString ();
788
+ }
789
+
790
+ Local<Value> targetDbValue =
791
+ options->Get (env->context (), target_db_string).ToLocalChecked ();
792
+
793
+ if (!targetDbValue->IsUndefined ()) {
794
+ if (!targetDbValue->IsString ()) {
795
+ THROW_ERR_INVALID_ARG_TYPE (
796
+ env->isolate (),
797
+ " The \" options.targetDb\" argument must be a string." );
798
+ return ;
799
+ }
800
+
801
+ dest_db =
802
+ Utf8Value (env->isolate (), targetDbValue.As <String>()).ToString ();
803
+ }
804
+
805
+ Local<Value> progressValue =
806
+ options->Get (env->context (), progress_string).ToLocalChecked ();
807
+
808
+ if (!progressValue->IsUndefined ()) {
809
+ if (!progressValue->IsFunction ()) {
810
+ THROW_ERR_INVALID_ARG_TYPE (
811
+ env->isolate (),
812
+ " The \" options.progress\" argument must be a function." );
813
+ return ;
814
+ }
815
+
816
+ progressFunc = progressValue.As <Function>();
817
+ }
818
+ }
819
+
820
+ Local<Promise::Resolver> resolver = Promise::Resolver::New (env->context ())
821
+ .ToLocalChecked ()
822
+ .As <Promise::Resolver>();
823
+
824
+ args.GetReturnValue ().Set (resolver->GetPromise ());
825
+
826
+ BackupJob* job = new BackupJob (
827
+ env, db, resolver, source_db, *destFilename, dest_db, rate, progressFunc);
828
+ job->ScheduleBackup ();
829
+ }
830
+
536
831
void DatabaseSync::CustomFunction (const FunctionCallbackInfo<Value>& args) {
537
832
DatabaseSync* db;
538
833
ASSIGN_OR_RETURN_UNWRAP (&db, args.This ());
@@ -1718,6 +2013,7 @@ static void Initialize(Local<Object> target,
1718
2013
SetProtoMethod (isolate, db_tmpl, " close" , DatabaseSync::Close);
1719
2014
SetProtoMethod (isolate, db_tmpl, " prepare" , DatabaseSync::Prepare);
1720
2015
SetProtoMethod (isolate, db_tmpl, " exec" , DatabaseSync::Exec);
2016
+ SetProtoMethod (isolate, db_tmpl, " backup" , DatabaseSync::Backup);
1721
2017
SetProtoMethod (isolate, db_tmpl, " function" , DatabaseSync::CustomFunction);
1722
2018
SetProtoMethod (
1723
2019
isolate, db_tmpl, " createSession" , DatabaseSync::CreateSession);
0 commit comments