Skip to content

Commit 632edd3

Browse files
committed
sqlite, test: expose sqlite online backup api
1 parent 76b80b1 commit 632edd3

File tree

3 files changed

+384
-0
lines changed

3 files changed

+384
-0
lines changed

src/node_sqlite.cc

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
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;
2930
using v8::FunctionCallbackInfo;
3031
using v8::FunctionTemplate;
3132
using v8::Global;
33+
using v8::HandleScope;
3234
using v8::Int32;
3335
using v8::Integer;
3436
using v8::Isolate;
@@ -40,6 +42,7 @@ using v8::NewStringType;
4042
using v8::Null;
4143
using v8::Number;
4244
using v8::Object;
45+
using v8::Promise;
4346
using v8::SideEffectType;
4447
using v8::String;
4548
using 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+
84105
inline 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+
131320
class 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+
536834
void 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);

src/node_sqlite.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class DatabaseSync : public BaseObject {
5757
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
5858
static void Prepare(const v8::FunctionCallbackInfo<v8::Value>& args);
5959
static void Exec(const v8::FunctionCallbackInfo<v8::Value>& args);
60+
static void Backup(const v8::FunctionCallbackInfo<v8::Value>& args);
6061
static void CustomFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
6162
static void CreateSession(const v8::FunctionCallbackInfo<v8::Value>& args);
6263
static void ApplyChangeset(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -81,6 +82,7 @@ class DatabaseSync : public BaseObject {
8182
bool enable_load_extension_;
8283
sqlite3* connection_;
8384

85+
std::set<sqlite3_backup*> backups_;
8486
std::set<sqlite3_session*> sessions_;
8587
std::unordered_set<StatementSync*> statements_;
8688

0 commit comments

Comments
 (0)