88#include " node_errors.h"
99#include " node_mem-inl.h"
1010#include " sqlite3.h"
11+ #include " threadpoolwork-inl.h"
1112#include " util-inl.h"
1213
1314#include < cinttypes>
@@ -29,6 +30,7 @@ using v8::FunctionCallback;
2930using v8::FunctionCallbackInfo;
3031using v8::FunctionTemplate;
3132using v8::Global;
33+ using v8::HandleScope;
3234using v8::Int32;
3335using v8::Integer;
3436using v8::Isolate;
@@ -40,6 +42,7 @@ using v8::NewStringType;
4042using v8::Null;
4143using v8::Number;
4244using v8::Object;
45+ using v8::Promise;
4346using v8::SideEffectType;
4447using v8::String;
4548using v8::TryCatch;
@@ -81,6 +84,24 @@ inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate,
8184 return e;
8285}
8386
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+
84105inline MaybeLocal<Object> CreateSQLiteError (Isolate* isolate, sqlite3* db) {
85106 int errcode = sqlite3_extended_errcode (db);
86107 const char * errstr = sqlite3_errstr (errcode);
@@ -128,6 +149,174 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) {
128149 isolate->ThrowException (error);
129150}
130151
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 || backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED)) {
229+ Local<Object> e = Local<Object>();
230+
231+ CreateSQLiteError (env ()->isolate (), backup_status_).ToLocal (&e);
232+
233+ Cleanup ();
234+
235+ resolver->Reject (env ()->context (), e).ToChecked ();
236+
237+ return ;
238+ }
239+
240+ int total_pages = sqlite3_backup_pagecount (pBackup_);
241+ int remaining_pages = sqlite3_backup_remaining (pBackup_);
242+
243+ if (remaining_pages != 0 ) {
244+ Local<Function> fn =
245+ Local<Function>::New (env ()->isolate (), progressFunc_);
246+
247+ if (!fn.IsEmpty ()) {
248+ Local<Value> argv[] = {
249+ Integer::New (env ()->isolate (), total_pages),
250+ Integer::New (env ()->isolate (), remaining_pages),
251+ };
252+
253+ TryCatch try_catch (env ()->isolate ());
254+ fn->Call (env ()->context (), Null (env ()->isolate ()), 2 , argv)
255+ .FromMaybe (Local<Value>());
256+
257+ if (try_catch.HasCaught ()) {
258+ Cleanup ();
259+
260+ resolver->Reject (env ()->context (), try_catch.Exception ()).ToChecked ();
261+
262+ return ;
263+ }
264+ }
265+
266+ // There's still work to do
267+ this ->ScheduleWork ();
268+
269+ return ;
270+ }
271+
272+ Local<String> message =
273+ String::NewFromUtf8 (
274+ env ()->isolate (), " Backup completed" , NewStringType::kNormal )
275+ .ToLocalChecked ();
276+
277+ Local<Object> e = Local<Object>();
278+ CreateSQLiteError (env ()->isolate (), pDest_).ToLocal (&e);
279+
280+ int dest_status = Cleanup ();
281+
282+ if (dest_status == SQLITE_OK) {
283+ resolver->Resolve (env ()->context (), message).ToChecked ();
284+ } else {
285+ resolver->Reject (env ()->context (), e).ToChecked ();
286+ }
287+ }
288+
289+ private:
290+ int Cleanup () {
291+ int status = 0 ;
292+
293+ if (pBackup_) {
294+ sqlite3_backup_finish (pBackup_);
295+ }
296+
297+ if (pDest_) {
298+ status = sqlite3_errcode (pDest_);
299+ sqlite3_close (pDest_);
300+ }
301+
302+ return status;
303+ }
304+
305+ // https://github.com/nodejs/node/blob/649da3b8377e030ea7b9a1bc0308451e26e28740/src/crypto/crypto_keygen.h#L126
306+ int backup_status_;
307+ Environment* env () const { return env_; }
308+ sqlite3* pDest_;
309+ sqlite3_backup* pBackup_;
310+ Environment* env_;
311+ DatabaseSync* source_;
312+ Global<Promise::Resolver> resolver_;
313+ Global<Function> progressFunc_;
314+ std::string source_db_;
315+ std::string destination_name_;
316+ std::string dest_db_;
317+ int pages_;
318+ };
319+
131320class UserDefinedFunction {
132321 public:
133322 explicit UserDefinedFunction (Environment* env,
@@ -533,6 +722,115 @@ void DatabaseSync::Exec(const FunctionCallbackInfo<Value>& args) {
533722 CHECK_ERROR_OR_THROW (env->isolate (), db->connection_ , r, SQLITE_OK, void ());
534723}
535724
725+ // database.backup(destination, { sourceDb, targetDb, rate, progress: (total,
726+ // remaining) => {} )
727+ void DatabaseSync::Backup (const FunctionCallbackInfo<Value>& args) {
728+ Environment* env = Environment::GetCurrent (args);
729+
730+ if (!args[0 ]->IsString ()) {
731+ THROW_ERR_INVALID_ARG_TYPE (
732+ env->isolate (), " The \" destination\" argument must be a string." );
733+ return ;
734+ }
735+
736+ int rate = 100 ;
737+ std::string source_db = " main" ;
738+ std::string dest_db = " main" ;
739+
740+ DatabaseSync* db;
741+ ASSIGN_OR_RETURN_UNWRAP (&db, args.This ());
742+
743+ THROW_AND_RETURN_ON_BAD_STATE (env, !db->IsOpen (), " database is not open" );
744+
745+ Utf8Value destFilename (env->isolate (), args[0 ].As <String>());
746+ Local<Function> progressFunc = Local<Function>();
747+
748+ if (args.Length () > 1 ) {
749+ if (!args[1 ]->IsObject ()) {
750+ THROW_ERR_INVALID_ARG_TYPE (env->isolate (),
751+ " The \" options\" argument must be an object." );
752+ return ;
753+ }
754+
755+ Local<Object> options = args[1 ].As <Object>();
756+ Local<String> progress_string =
757+ FIXED_ONE_BYTE_STRING (env->isolate (), " progress" );
758+ Local<String> rate_string = FIXED_ONE_BYTE_STRING (env->isolate (), " rate" );
759+ Local<String> target_db_string =
760+ FIXED_ONE_BYTE_STRING (env->isolate (), " targetDb" );
761+ Local<String> source_db_string =
762+ FIXED_ONE_BYTE_STRING (env->isolate (), " sourceDb" );
763+
764+ Local<Value> rateValue =
765+ options->Get (env->context (), rate_string).ToLocalChecked ();
766+
767+ if (!rateValue->IsUndefined ()) {
768+ if (!rateValue->IsInt32 ()) {
769+ THROW_ERR_INVALID_ARG_TYPE (
770+ env->isolate (),
771+ " The \" options.rate\" argument must be an integer." );
772+ return ;
773+ }
774+
775+ rate = rateValue.As <Int32>()->Value ();
776+ }
777+
778+ Local<Value> sourceDbValue =
779+ options->Get (env->context (), source_db_string).ToLocalChecked ();
780+
781+ if (!sourceDbValue->IsUndefined ()) {
782+ if (!sourceDbValue->IsString ()) {
783+ THROW_ERR_INVALID_ARG_TYPE (
784+ env->isolate (),
785+ " The \" options.sourceDb\" argument must be a string." );
786+ return ;
787+ }
788+
789+ source_db =
790+ Utf8Value (env->isolate (), sourceDbValue.As <String>()).ToString ();
791+ }
792+
793+ Local<Value> targetDbValue =
794+ options->Get (env->context (), target_db_string).ToLocalChecked ();
795+
796+ if (!targetDbValue->IsUndefined ()) {
797+ if (!targetDbValue->IsString ()) {
798+ THROW_ERR_INVALID_ARG_TYPE (
799+ env->isolate (),
800+ " The \" options.targetDb\" argument must be a string." );
801+ return ;
802+ }
803+
804+ dest_db =
805+ Utf8Value (env->isolate (), targetDbValue.As <String>()).ToString ();
806+ }
807+
808+ Local<Value> progressValue =
809+ options->Get (env->context (), progress_string).ToLocalChecked ();
810+
811+ if (!progressValue->IsUndefined ()) {
812+ if (!progressValue->IsFunction ()) {
813+ THROW_ERR_INVALID_ARG_TYPE (
814+ env->isolate (),
815+ " The \" options.progress\" argument must be a function." );
816+ return ;
817+ }
818+
819+ progressFunc = progressValue.As <Function>();
820+ }
821+ }
822+
823+ Local<Promise::Resolver> resolver = Promise::Resolver::New (env->context ())
824+ .ToLocalChecked ()
825+ .As <Promise::Resolver>();
826+
827+ args.GetReturnValue ().Set (resolver->GetPromise ());
828+
829+ BackupJob* job = new BackupJob (
830+ env, db, resolver, source_db, *destFilename, dest_db, rate, progressFunc);
831+ job->ScheduleBackup ();
832+ }
833+
536834void DatabaseSync::CustomFunction (const FunctionCallbackInfo<Value>& args) {
537835 DatabaseSync* db;
538836 ASSIGN_OR_RETURN_UNWRAP (&db, args.This ());
@@ -1718,6 +2016,7 @@ static void Initialize(Local<Object> target,
17182016 SetProtoMethod (isolate, db_tmpl, " close" , DatabaseSync::Close);
17192017 SetProtoMethod (isolate, db_tmpl, " prepare" , DatabaseSync::Prepare);
17202018 SetProtoMethod (isolate, db_tmpl, " exec" , DatabaseSync::Exec);
2019+ SetProtoMethod (isolate, db_tmpl, " backup" , DatabaseSync::Backup);
17212020 SetProtoMethod (isolate, db_tmpl, " function" , DatabaseSync::CustomFunction);
17222021 SetProtoMethod (
17232022 isolate, db_tmpl, " createSession" , DatabaseSync::CreateSession);
0 commit comments